Skip to content

Adding New Operators

Dave DeLong edited this page · 1 revision

You can define your own operators using DDMathParser.

There are two steps to adding new operators:

  1. Allocating a new DDMathOperator object
  2. Adding it with the appropriate relative precedence

You should note, however, that adding new operators is not a thread-safe operation. Adding new operators while expression evaluation is occurring on a separate queue is undefined. For best results, you should declare your new operators prior to any performing any parsing.

Additionally, adding new operators is applicable to all DDMathEvaluator objects; it is not possible to add a new operator for a specific Evaluator or Parser instance.

Allocating a new DDMathOperator object

In order to allocate a new operator, you must supply the following information:

  • The NSString indicating the function to which the operator resolves (this will likely be a custom function)
  • The NSArray of NSString objects indicating how the operators are recognized in a string
  • The arity of the operator
  • The associativity of the operator

The arity of the operator indicates how many parameters it takes: in other words, whether it's a unary or binary operator.

The associativity of the operator plays a part in the order of operations. For binary operators, it indicates whether the left-most or right-most copy of the operator should be evaluated first. For unary operators, it indicates which side of the operator has the term on which the operator evaluates.

For example, the factorial operator (!) is a left-associative unary operator. It only takes a single parameter (unary), and term appears to the left of the operator (left associative). The bitwise-not operator (~) is a right-associative unary operator, because it likewise takes a single parameter, but operates on a term that appears to its right.

If you decide to add a new operator, there are a couple of caveats:

  • You may not create a new operator that has the same token as an existing operator. Attempting to do so will throw an exception.
  • You may not create a new operator with a "Unknown" arity. This will also throw an exception.
  • Operators may not begin with a digit (0-9), a period (.), a dollar sign ($), a single quote ('), a double quote ("), begin with whitespace, or end with whitespace. Passing a token that does not meet these requirements will throw an exception. These characters are illegal because of the order of parsing: first numbers are parsed (which can begin with a digit or a period), followed by variables (which can begin with a dollar sign, a single quote, or a double quote). Additionally, whitespace in an expression string is seen as a logical break, so operators cannot begin with whitespace. However, they may contain whitespace. "is equal to" is an acceptable token for an operator, for example.

Specifying Operator Precedence

In order for parsing to succeed, you will need to specify the appropriate operator precedence. Because the precedence property of a DDMathOperator object is an implementation detail, you indicate the precedence by saying that your new operator has lower, higher, or the same precedence as an existing operator.

Example

Let's say we wanted to add a new operator, called "multipl-add". It will be a binary operator defined by the token "*+" that multiplies its two operands, and then adds that value to the sum of the two operands, like so:

a *+ b -> a + b + (a * b)

We would first create it like this:

DDMathOperator *multipladdOperator = [[DDMathOperator alloc] initWithOperatorFunction:@"muladd" tokens:@[@"*+"] arity:DDOperatorArityBinary associativity:DDOperatorAssociativityLeft];

It's a binary operator, and by default, all binary operators should be left associative.

Next, we need to define the relative precedence of this operator. Since it's inherently based on multiplication, we want it to be at the same precedence as the multiplication and division operators:

DDMathOperator *multiplyOperator = [DDMathOperator infoForOperatorFunction:DDOperatorMultiply];
[[DDMathOperatorSet defaultOperatorSet] addOperator:multipladdOperator withSamePrecedenceAsOperator:multiplyOperator];

Finally, we would define our custom "muladd" function:

[[DDMathEvaluator defaultMathEvaluator] registerFunction:^DDExpression *(NSArray *args, NSDictionary *vars, DDMathEvaluator *eval, NSError *__autoreleasing *error) {
    if (args.count != 2) {
        *error = [NSError errorWithDomain:DDMathParserErrorDomain code:DDErrorCodeInvalidNumberOfArguments userInfo:@{NSLocalizedDescriptionKey: @"muladd requires 2 arguments"}];
        return nil;
    }

    DDExpression *first = [args objectAtIndex:0];
    DDExpression *second = [args objectAtIndex:1];

    DDExpression *multiply = [DDExpression functionExpressionWithFunction:DDOperatorMultiply arguments:@[first, second] error:error];
    DDExpression *sum = [DDExpression functionExpressionWithFunction:@"sum" arguments:@[first, second, multiply] error:error];
    return sum;

} forName:@"muladd"];

Now, we can evaluate strings that contain our custom operator:

NSNumber *result = [@"2 *+ 3" numberByEvaluatingString]; // returns @11
Something went wrong with that request. Please try again.