Clearly when providing any new computational environment it is pretty hard to
please everyone. There is always going to be come particular computation that
is required - such as computing the sin of an angle for example.
The formula model has therefore been developed from the beginning with customisation in mind.
This static method on the EVAL_SEXPR class in the
cutthecrap.formula package allows the dictionary of formulas
to be extended at runtime.
In fact this is how the standard primitives are registered within the package:
registerSexpress("->", EVAL_LOOKUP.class);
Any registered class must be derived from EVAL_SEXPR, which also
provides utility methods to aid processing. Here is the full definition of EVAL_PLUS.
package cutthecrap.formula;
public class EVAL_PLUS extends EVAL_SEXPR {
public Object recalc() {
double result = 0;
Iterator args = getNodes();
while (args.hasNext()) {
result += makeDouble(args.next(), 0);
}
return new Double(result);
}
}
The makeDouble method handles any recursive formula evaluations.
Consider a requirement to monitor a changing value and as a side-effect write its value to
standard out. To make it easy we can derive from EVAL_STR which also means
that we get the benefit for being able to easily define feedback messages.
package cutthecrap.formula;
public class EVAL_WATCH extends EVAL_STR {
public Object recalc() {
System.out.println(makeString(super.recalc(), "", parent));
return null;
}
}
Once registered by registerSexpress("watch", EVAL_WATCH.class); it could
be used as follows:
gpm.define("monitor", "(watch 'Value now : ' (-> value))");
Now try setting a value:
gpm.set("value", "start");
Value now : start
gpm.set("value", "change");
Value now : change
Keen-eyed readers may have spotted the makeString method usd within
recalc.
makeString is one of several similar methods that resolve values and
provide defaults if the value is null.
This is the common signatire used:
Type makeType(Object value, Type default, IGPO parent);
Here is the EVAL_TIMES primitive definition:
public class EVAL_TIMES extends EVAL_SEXPR {
public Object recalc(IGPOMap parent) {
double result = 0;
Iterator args = getNodes();
if (args.hasNext()) {
result = makeDouble(args.next(), 0, parent);
while (args.hasNext()) {
result *= makeDouble(args.next(), 0, parent);
}
}
return new Double(result);
}
}
The EVAL_TIMES primitive will be called to evaluate expressions like
(* 2 3) and (* 2 (-> value)).
In the second expression the result of the (-> value) expression must
be multiplied by 2, the makeDouble method will fully resolve
the value of the second argument, evaluating the lookup and then converting
to the double value.
Here are the currently implemented utilities for EVAL_SEXPR derived
formulas.
protected double makeDouble(Object obj, double def, IGPOMap parent); protected short makeShort(Object obj, int def, IGPOMap parent); protected boolean makeBoolean(Object obj, boolean def, IGPOMap parent); protected int makeInt(Object obj, int def, IGPOMap parent); protected String makeString(Object obj, String def, IGPOMap parent);
To ease the configuration of new libraries of primitives, they can be registered via an xml registration file.
A public static method importDict(String xmlFile)EVAL_SEXPR.
Here is an example file:
<formula-dict> <define name='->' class='cutthecrap.formula.EVAL_LOOKUP'/> <define name='+' class='cutthecrap.formula.math.EVAL_PLUS'/> <define name='stddev' class='cutthecrap.formula.stats.EVAL_STDDEV'/> </formula-dict>
Another level of indirection allows these definition files to be referenced from
a GPO description file used to initialize a system, suppose the path to the
above file was "/mydef/defs.xml", here is how it would be included when the
system was initialized from the description file:
<desc>
<gpo objectManager="cutthecrap.oms.worm.ObjectManager"
storefile="/ctc/db/test.wo"/>
<formula-dict import='/mydef/defs.xml'/>
</desc>
If the above file was saved as "/ctc/sys/mysys.xml" you would use the OMClient
contructor that takes a definion file to initialize the system:
OMClient client = new OMClient("/ctc/sys/mysys.xml");
You will then be able to define formulas using the new primitives.
You will need to add the jar file containing the formulas to
the webapps/alchemist/WEB-INF/lib folder in your tomcat
system.
You should then edit the login.jsp file in the custom
folder to add the following attribute to the gpo:init tag:
formulaDict="formuladefs.xml"
You can include multiple dictionary files by using the ';' separator:
formulaDict="formuladefs.xml;moredefs.xml"
The files must be located relative to the WEB-INF directory.