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.

registerSexpress(String foo, Class clzz)

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);

EVAL_SEXPR

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.

Specializing existing primitives

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

Value Resolving Utilities

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);

Primitive Libraries

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>

Specifying Primitive Libraries

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.

New Primitives Within Alchemist Web Application

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.