Adding new QScript operators

QScript already supports over 50 operators, but it is possible to add more.

Operators

If you look at the operators you will see four distinct types

Operator Type Examples Format Operator Constant
INFIX_SYMBOL +
>=
AND
op1 symbol op2 An infix symbol always has two operands (arguments)
FUNCTION sin(a)
IF(cond)
symbol(parameters) This applies to all the math functions and to the conditional / loop functions. A function has zero or more parameters (operands).
CONSTANT PI
EOL
TRUE
symbol The constant does not have to be numeric, it can also be a String or boolean
MARKER ELSE
; , ()
symbol These are used to mark positions or separate sections in the script.

It is not possible to add your own markers, loop and conditional statements, but it is possble to define your own functions and constants so they become can be used in your scripts.

When adding new operators it is important to ensure that the correct order of precedence is maintained, so unless you really know what you are doing I suggest that you stick with adding

  • constants (Priority 40) and
  • functions (Priority 30)

Some points you should be aware of

  1. QScript is case sensitive.
  2. every symbol must be unique,
  3. function overloading, where the same symbol is used but with a different number of parameters, is not supoorted in QScript,

Adding the 'hypot' Function

We are going to keep with Pythagoras and add a new function operator to calculate the hypotenuse of a right angled triangle from the length of the other two sides.

The first thing to do is decide on the symbol for our new function, in this case we are going to call it hypot. Obviously this function requires two parameters for the length of the other two sides and will calculate and return the length of the hypotenuse.

This is a two stage process -

  1. create a class to resolve our new symbol (i.e. perform the required action) and then
  2. add this new operator to the operator set.

In this guide we are going to call the class HypotenuseFunction and it is important that this is a top level class which is no problem in Eclipse or Netbeans simply create a new class. In the Processing IDE there are two options

  1. create a new tab and call it 'HypotenuseFunction.java' for the source code
  2. create the class in a pde tab and promote it to a top-level class with the static keyword.

In this guide I will demonstrate the second option. So in a pde tab enter the following code

public static class HypotenuseFunction extends Operator {

  public HypotenuseFunction(String symbol, int nbrArgsNeeded, int priority, int type) {
    super(symbol, nbrArgsNeeded, priority, type);
  }

  public Argument resolve(Script script, Token token, Argument[] args, Object... objects) 
  throws EvaluationException {
    testForUninitialisedVars(script, args);
    Argument a0 = args[0];
    Argument a1 = args[0];
    if (a0.isNumeric() && a1.isNumeric()) {
      double s0 = a0.toDouble(), s1 = a1.toDouble();
      double hyp = Math.sqrt(s0*s0 + s1*s1);
      script.fireEvent(HpotCalculatedEvent.class, null, token, new Argument(new Double(hyp)));
      return new Argument(new Double(hyp));
    }

    // If we get here then the arguments are invalid
    handleInvalidArguments(script, token);
    return null;
  }
}

At first sight this might seem complicated but most of this code is common for for any new function class the only differencs are

  1. the class name (see lines 1 and 3)
  2. the contents of the resolve method (lines 9 to 22 inclusive)
    1. the test for uninitialised variables (line 9) should be the first line of the method
    2. the following code upto the blank line should perform the operation and if successful it must return an object of type Argument or null before -
    3. if there was an error then your program should drop through to line 20

The import statements (lines 1 to 7) are important and should be included unchanged.

The resolve method explained

Line No Comments
9 If the new operator has 1 or more arguments (parameters) then add this line exactly as shown . It tests each argument in turn and if any of them is an uninitialised varaible it will fire an EvaluationErrorEvent and throw an EvaluationException causing the resolve method to be exited immediately.
10 & 11 Add one line for each parameter.
12 to 16 incl. So now it tests the data types of the arguments and if acceptable it will perform the operator's action and return a new Argument with the result (you can also return it the result is not needed later).
If the required action depends on the arguments' data types then add further if-construct blocks can be added.
20 If this line is reached then the arguments data types are invalid for this and it will fire an EvaluationErrorEvent and throw an EvaluationException.

So the next step is to add this new operator to the default operator set and this is done with

HypotenuseFunction h;
h = new HypotenuseFunction("hypot", 2, 30, Operator.FUNCTION);
OperatorSet.get().addOperator(h);

The first part creates an object of type HypotenuseFuction with the parameters

  1. the operator symbol
  2. the number of parameters
  3. the priority (use 30 for fumctions)
  4. the operator type (see above)

then we simple add it to the current operator set.

The hypot function can now be used like any other function -

String code = "$h = hypot(3, 4); println('Hypotenuse = ' + $h)";
Result r = Solver.evaluate(code);

 

The PHI constant

Adding new constants is basically the same process. In this example I am going to add a constant called PHI to hold the value of the Golden Ratio

So here is the class (in Processing the class should be declared static or placed in a sparate tab called PHI_constant.java)

public static class PHI_constant extends Operator {

  public PHI_constant(String symbol, int nbrArgs, int priority, int type) {
    super(symbol, nbrArgs, priority, type);
  }

  public Argument resolve(Script script, Token token, Argument[] args, Object... objects) 
  throws EvaluationException {
    return new Argument(new Double(1.618033988749895));
  }
}

and here is how to add it to the operator set and use it.

  PHI_constant p = new PHI_constant("PHI", 0, 40, Operator.CONSTANT);
  OperatorSet.get().addOperator(p);

  String code = "println('Golden Ratio = ' + PHI); END(PHI)";
  Result r = Solver.evaluate(code);

Notice that the script uses the END method so that the value of PHI is returned in the result.

Was that not easy-peasy?