First release of JEvaluate can be downloaded here
Usage :
Workspace ws = new Workspace(true);
//true means register default functions
ws.add(new Variable("a", 'n', "(6+10.33)*(15-(33+55))"));
/* create and add a new Variable
a: variable's name
b:variabl's type 'n' means Numeric type
"(6+10.33)*(15-(33+55))" : Mathematical expression see below
*/
ws.add(new Variable("b", 't', "if(a=1;'true';'false')"));
// add a new variable containing if function, see below
System.out.println(ws.get("b"));
// get the value of b from the workspace
Expressions can include:
- Constants : Numeric (ex.: 156.33), Text (ex.: 'a text'), Date (ex. : #31/05/2005#),
and boolean (true/false)
- Operators : Arithmetic (+, -, *, / and % (modulo)), comparaison (=, !=, , =), Logical (&&(and), || (or))
- Variables : Names of other variables, must be declared before used.
- Functions : The parser contains a set of default functions for handling data (Text,
numeric, dates) you can create your own functions by implementing the Function
Interface (cf. below). Standard functions are :
Math : Standard function of Math class that accept double argument:
abs, sin, cos, tan, sinh, cosh, tanh, asin, acos, atan, log, log10, exp, sqrt, pow
Other : Text and Date functions :
daysBetween(dateFrom, dateTo), yearsBetween(…), length(Text)
the Method printFunctions in the Workspace object print all functions registered with
its parser
for example here is a sample output
sin(Number)
return the absolute value of number
cos(Number)
return the absolute value of number
cos(Number)
return the absolute value of number
tangh(Number)
return the absolute value of number
startsWith(String)
Returns the length of Text
pow(Number, Power)
return the absolute value of number
atan(Number)
return the absolute value of number
index(String, SubString)
Returns the index within this string of the first
occurrence of the specified substring
log(Number)
return the absolute value of number
exp(Number)
return the absolute value of number
monthsBetween(String, Prefix)
return true if a String starts with a prefix
sinh(Number)
return the absolute value of number
like(String, Pattern)
Tests if a String matchs the pattern
asin(Number)
return the absolute value of number
toString(ArgToConvert)
converts the argument to Text
toNum(ArgToConvert)
converts the argument provided on Numeric value
acos(Number)
return the absolute value of number
abs(Number)
return the absolute value of number
if(condition, expressionIfTrue, expressionIfFalse)
return the first value if condition is true, the
second else
monthsBetween(dateFrom, daetTo)
return number of months between tow dates
log10(Number)
return the absolute value of number
indexFrom(String, SubString, IndexFrom)
Returns the index within this string of the first
occurrence of the specified substring, starting the search
at the specified index.
tan(Number)
return the absolute value of number
daysBetween(dateFrom, daetTo)
return number of days between tow dates
yearsBetween(dateFrom, daetTo)
return number of years between tow dates
sqrt(Number)
return the absolute value of number
endsWith(String, suffix)
return true if a String ends with a suffix
Extending the parser
The workspace uses an internal parser to parses expressions. You can obtain the actual
instance of the parser by calling getParser method of the workspace object.
As mentioned earlier you can extend the parser by creating your own functions. A
function must implement the Function interface or extends the AbstractFunction class.
But you must before understand some terms.
Symbols
Symbols gives information about names and types of variables used in an expression,
the Symbol interface define methods for extracting the required informations:
public interface Symbole {
public abstract int getType();
// Types are constants defined in the Type class
// Type.Nymeric, Type.Text, Type.Date, Type.BOOLEAN
public abstract String getName();
public abstract boolean referTo(String varName, Environment
symboles) throws VarNotFoundException;
return true if this symbol uses reference to varName as defined in the
Environment sybols (cf later). It allows to avoid circular references
(direct or indirect) between tow variables */
}
Nodes
Nodes are operands that composes an expression, consider the simple expression below:
5+sin(2)
This expression can be represented as a composition of operands (Nodes)
Arithmetic expression Node (5+sin(2))
+ : operator
5 : Numeric Constant Node
Sin(2) : Sin Function Node
2 : Numeric Constant Node
As illustrated above, the parser parses expressions and create a Node for every
construct in the expressions, because expressions can includes nested expressions (like
sin(2+3)), Nodes can also include other nodes.
Nodes are actually instances of object that implements the Node interface. It extends
the Symbol interface by providing the tow additional methods
public interface Node extends Symbole{
public abstract Object getValue(Environment env) throws
Exception;
/* gives the value of this node referring to the Environment env,
for example Functins uses the Environment to get values of their
arguments, performs appropriate calculations and then return the
result*/
public abstract Node reduce();
/* this optional method can be used to optimize some nodes, for
example, an expression 2+3 can be evaluated "at compile time" by the
ArithmeticExpressionNode and then generate a numeric constant Node
5*/
}
Environment
The Environment interface defines methods for storing and retrieving object by keys
(like Hash tables). This interface serves for many purposes:
- When parsing a variable : for example adding tow variables "x = 2+3" and
"y=x+2" tow the wokspace, involves many operations:
The workspace creates an instance of an Environment to hold symbols
Environment symbols = new EnvironmentImpl(); //this is the
default implementation of Environment
It uses the parser instance for parsing expression
Node node = parser.parse(expression /*2+3*/, symbols,
variableName/*x*/)
Then add the "x" variable to the Environment
symbols.put("x", node);
When parsing the second variable "y", the parser encounters a reference to x, it
then asks to the Environment symbols for that identifier, checks his type and
eventually for circular references between x and y, and finally, if there are no
errors, return a node representing the parsed expressions.
- When evaluating variables, suppose we have a list of parsed variables x and y and
we want to evaluate them:
First we create an instance of an Environment to hold values
Environment values = new EnvironmentImpl();
Then we ask the node "x" for his value and we add them to the Environment
Object value = x.getValue(values);
Values.put("x", value);
Then we ask y.getValue(values), but "y" contains a reference to a variable
named "x", it uses the provided Environment to get the value of "x", performs
the addition and the return the result.
The sample code following illustrate the use of Environment and Parser
import parser.*;
class Test {
public static void main(String[] args) throws Exception{
Parser parser = new Parser();
parser.registerDefaultFunctions();
Environment symbols = new EnvironmentImpl();
Environment values = new EnvironmentImpl();
Node x = parser.parse("2+3", symbols, "x", false);
symbols.put("x", x);
Node y = parser.parse("x*5", symbols, "y", true/*add "y"
automtically to symbols*/);
Object vx = x.getValue(values);
values.put("x", vx);
Object vy = y.getValue(values);
System.out.println("x = "+vx);
System.out.println("y = "+vy);
}
}
Variables
The Variable class serves as rapid way to create variables, can be used as
symbols, nodes and can be serialized to disk
Example creation:
Variable a = new Variable("a", 'n' /*or Type.Numeric*/,
"(6+10.33)*(15-(33+55))"));
/*Type can be specified as Integer constants: Type.NUMERIC,
Type.TEXT; Type.DATE, Type.BOOLEAN
Or characters 'n': numeric, 't': Text, 'd': Date, 'b': Boolean */
Example parsing :
variable.parse(parser, symbols, true /* true for adding variable to
symbols*/);
Creating Functions
The Function interface defines methods for creating your own functions:
public interface Function extends Node{
public String[] getParams(); // optional
public String getDescription(); // optional
public void setArg(int arg, Node value) throws Exception;
}
Because Function extends Node (it self extending Symbol), you must also
implements this interface. Total methods that must be implemented are:
public abstract int getType();
public abstract String getName();
public abstract boolean referTo(String propName, Environment symboles)
throws VarNotFoundException;
public abstract Object getValue(Environment env) throws Exception;
public void setArg(int arg, Node value) throws Exception;
public abstract Node reduce(); // optional
public String[] getParams(); // optional, used for documentation purpose
public String getDescription(); // optional, used for documentation purpose
You can extends the AbstractFunction Class to avoid defining all methods, for example
Class MyFunction extends AbstractFunction {
// methods you must define
public abstract void setArg(int arg, Node value) throws Exception ;
public abstract String getName();
public abstract int getType();
public abstract Object getValue(Environment env) throws Exception;
public boolean referTo(String name, Environment env) throws
VarNotFoundException {
/* by default return false, you can implement this method by
simply calling referTo method of the args*/
}
}
When getting values from Nodes, you need to cast values returned as Object's, generally you
know the type of the node by asking the getType method (this is typically down in the setArg
method), then following the type, you cast to the appropriate value:
NUMERIC -> cast to Double
TEXT -> cast to String
Date -> cast to Date
BOOLEAN -> cast to Boolean.
Example, Function for calculating the maximum of tow numbers:
import parser.AbstractFunction;
import parser.ArgOutOfBoundsException;
import parser.Environment;
import parser.IncompatibleTypeException;
import parser.Node;
import parser.Type;
public class MaxFunctionNode extends AbstractFunction {
private Node one, tow;
public void setArg(int arg, Node value) throws Exception {
if(value.getType()!=Type.NUMERIC)
throw new IncompatibleTypeException(value.getType(),
Type.NUMERIC);
switch(arg){
case 1:
one=value;
break;
case 2:
tow=value;
break;
default:
throw new ArgOutOfBoundsException(arg);
}
}
public String getName() {
return "max";
}
public int getType() {
return Type.NUMERIC;
}
public Object getValue(Environment env) throws Exception {
double v1 = ((Double)one.getValue(env)).doubleValue();
double v2 = ((Double)tow.getValue(env)).doubleValue();
return new Double(Math.max(v1, v2));
}
}
After creating this function, you can add it to the parser by calling
parser.registerFunction("max", MaxFunctionNode.class);
If a function already exists with the supplied name, the parser throws a
DuplicateFunctionException.
If you want override a function you must call overrideFunction
parser. overrideFunction("max", MaxFunctionNode.class);
if the overridden function does not exists, the parser add the function and return false.