CalcEngine Plus

User-authored calculations for .NET

Custom Functions

CalcEngine Plus allows developers to invent new functions to extend the built-in functions. Your custom functions look and behave just like the inbuilt functions (sin(), floor(), etc) - your users won't be able to tell the difference.

 

Custom Functions are needed in situations where you are 

 

What's the point?

When you have written and deployed a custom function library, your application users will have one or more new functions available to them, for example,  "MyFunc" that they can use in their own expressions:

MyFunc()
trunc(myfunc(mytag1, 23) / mytag2)

 

The results produced by your custom function are limited only by your imagination. You could perform database lookups, integrate an argument over time, call a web service (caching the result for speed purposes, of course) or run a custom algorithm.

Obviously your documentation or your application UI will have to inform your users about the new function, and what it's arguments mean!

You can specify the minimum and maximum number of arguments that your function expects, and the CalcEngine Plus parser will raise an error for you if those expectations are not met.

If your function operates on numeric arguments, your function can accept tagnames as arguments or any other calculated expressions. This is the default case. Alternatively, your function can tell the parser "these arguments have no meaning to anything but me, so don't try and parse them or interpret them as tagnames. This is vital if your function is performing database lookups using the user-supplied arguments as keys.

What do I have to build?

You can have multiple classes (i.e. several new functions) in your custom DLL. CalcEnginePlus will just find them by reflecting over the library every time the parser is instantiated. The parser can only have one custom library.

 

Simple Example

Let's walk through a simple example of a function that simply doubles the value of its argument. We'll call it Doubler, (because we're wildly imaginative like that). The usage syntax will be

doubler(x)

In our app, we'll have to create a class called Doubler.

Suggestion: In your Visual Studio solution file, you might want to create a Class Library project called CustomFunctions or similar, and add Doubler.cs in there.. Such a project will generate a file called CustomFunctions.dll, and it will contain ONLY your specified custom function classes. This will help the CalcEnginePlus parser load your file faster, by minimising the number of functions it has to find and check.


namespace MyApp
{
    using CalcEnginePlus;
    /// 
    /// A custom function that will double its argument
    /// 
    public class Doubler: CustomFunction
    {
        public override double Result { get; }
    }
}

This is what your Double class will look like initially when you inherit from CustomFunction and allow the interface to be implemented. You will have to add a reference to CalcEnginePlus.dll to your project in order to inherit your class from CustomFunction

We'll add a constructor, where we specify the minimum and maximum number of arguments we will permit. We don't have to, but this helps CalcEnginePlus generate nice error messages when your users get things wrong:


        public Doubler()
        {
            this.minArgs = 1;
            this.maxArgs = 1;
        }

To make it do something useful, let's flesh out the Result property:


        public override double Result
        {
            get
            {
                // Clear any pre-existing error message and check arg counts
                if (!this.ClearErrorAndCheckArgCounts())
                {
                    return double.NaN;
                }

                // Evaluate our one argument, which will force any error message to be generated
                var tempResult = this.operandList[0].Result;

                // Propagate any error message from the evaluation of the argument
                if (double.IsNaN(tempResult))
                {
                    this.ErrorMessage = this.operandList[0].ErrorMessage;
                }

                // And return our result according to the complex business logic of this function
                return tempResult * 2.0;
            }
        }

 

 

...aaand, that's it. You don't have to do anything special about propagating NaN values, that's all done for you. If your users pass you 1.0/0.0 as an argument, that's fine. If you set the ErrorMessage on error, as per the example, that's enough to propagate it up to your main application. There's some powerful stuff done behind the scenes for you.

In your main app, you have to specify the dll name every time you instantiate the CalcEnginePlus parser:


var parser = new Parser() { CustomFunctionLibrary = "CustomFunctions.dll" };

And you have to make sure that your CustomFunctions.dll is deployed to the same folder as your main application, because that's where CalcEnginePlus will look for it.

Next example: Careful coordination before asking for results - accumulation

Literal Arguments

What if you don't want the arguments interpreted or parsed in any way? Let's imagine that we want to build a function that returns the number of days that a given employee has been working for the company. Our users have asked for a syntax like

DaysEmployed(John, Doe)

In this function "John" and "Doe" are literal strings. There are no tags called "John" or "Doe", and we don't want the parser to try and evaluate "John" or "Doe" as expressions. Our DaysEmployed function just want the strings, because it will use them to look up the employee length of service in the database. using a stored procedure

 

TODO..: 

Mention OperandsOfType

Setting the CustomFunctionLibrary property

If there is a problem with the DLL specified in the CustomFunctionLibrary property, a CalcEngineException will be thrown. Inspect this exception for additional support information that should help you diagnose the problem quickly.