| CARVIEW |
|
jevaluate
|
| Summary | A mathematical expression evaluator |
|---|---|
| Categories | None |
| License | Lesser General Public License (LGPL) |
| Owner(s) | rootel77 |
Message from the owner(s)
First
release of JEvaluate can be downloaded here
Description:
JEvaluate provides a simple and easy way to
embed a java expression evaluator in an application. Unlike scripting languages
(jython, groovy…), it is not "programming orinted", and
the end user can understand the syntax quickly.
Features:
-
JEvaluate uses a simple type system : (there
is no double, float or others, but simply a numeric type)
-
Provides object-oriented approach to variables parsing and handling : variable are object that can be precompiled and
serialized to disk.
-
The parser can detect circular references between variables.
-
Functions can be easily added to the parser, you can have multiples
parsers, each with its own function set.
-
Support for internationalization, functions registered in a parser can
be simply renamed with a one method call.
-
The Workspace class can manage many variables and references between
them; it recalculates automatically the values if a variable is changed.
Usage Example:
|
//true means register default function Workspace ws = new Workspace(true); /* create and add a new Variable a: variable's name b:variabl's
type 'n' means Numeric type "10.1234567890123456789-3.123456789"
: Mathematical expression see below */ ws.add(new Variable("a", 'n',
"10.1234567890123456789-3.123456789")); //expression with if function and with reference to variable 'a' ws.add(new Variable("b", 't',
"if(a=7.0000000000123456789;'true';'false')")); // expression with reference to 'b' ws.add(new Variable("c", 't',
"if(b='true';'b is true';'b
is false')")); //get and print values of variables System.out.println(ws.get("a")); System.out.println(ws.get("b")); System.out.println(ws.get("c")); /*modify the variable 'a', the workspace will automatically update
variable that depends to it( here 'b' and 'c') */ ws.set("a", new
Variable("a", 'n', "5+6")); System.out.println(ws.get("a")); System.out.println(ws.get("b")); System.out.println(ws.get("c")); |
Expressions
can include:
- Constants : Numeric (ex.: 156.33), Text (ex.: 'a text'), Date (ex. : #
- 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).
|
the Method printFunctions in the Workspace
object print all functions registered with its parser for example here is a sample output
|
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 Symbol { public
int getType();
/* Types are constants defined in the Type class Type.NUMERIC,
Type.TEXT, Type.DATE, Type.BOOLEAN */ public
String getName(); public
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))
|
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 Symbol { public 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 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*/ } } |
Correspondence Type vs. Java
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 BigDecimal
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 { BigDecimal v1 = (BigDecimal
)one.getValue(env); BigDecimal v2 = (BigDecimal
)tow.getValue(env); Return (v1.compareTo(v2)>0)?v1:v2; } public boolean
referTo(String name, Environment env) throws VarNotFoundException
{ return (one.referTo(name,
env) || tow.referTo(name,
env)); } } |
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.
Arithmetic parameters (precision,
scale…)
You can specify various parameters for
arithmetic operations (precision of numbers, the significant digits for comparison) :
When instantiating the parser, the constructor
can take an instance of MathContext class (see
javaDoc):
public
Parser(MathContext mathCtxt)
You can also specify the number significant digits when comparing tow numbers
public void setCompScale(int compScale)
compScale the number of digits after the
decimal points) used for number comparison, for example, with a precision of 3,
the tow numbers 1.22350 and 1.22399 are considered to be equals. -1 means unlimited.
Internationalization
The parser provides methods to facilitate
internationalization:
-
first, you can provide a new name for any
function registered in this parser
public boolean renameFunction(String oldName, String newName).
-
you can also provide your own names for the
Boolean constants (default are true and false)
parser.TRUE_CONST = "vrai"
parser.FALSE_CONST = "faux"
(You must provide a
name according to Variable name rules, i.e. alpahanumeric
values and the '_' and '.' symbols)
| Powered by CollabNet | Feedback |
FAQ |
Press |
Developer tools
© 1995 - 2007 CollabNet. CollabNet is a registered trademark of CollabNet, Inc. |
