CalcEngine Plus

User-authored calculations for .NET

Implementing nested expressions or subexpressions

Many applications will benefit from sub-expressions: i.e. named entities which represent a calculated expression. CalcEngine Plus supports sub-expressions, which can be nested to any practical depth

Subexpression example

work = force * distance

power = work/time       

In the above example, the tag ‘work’ is a sub-expression – a named, user-defined formula.

How to support sub-expressions

Your application must keep track of tagnames and sub-expressions in its configuration database. CalcEngine Plus supports nested expressions via a “sub-expression server”. This is an object authored by you, the developer, that implements CalcEngine.ISubExpressionService. A sub-expression server is an optional object that you can pass into the CalcEngine.Parser constructor. Without it, your users can't make use of sub-expressions.

ISubExpressionService has only one method, GetSubExpressions(tagname), which returns a sub-expression (or null) for a given tagname:


        public string GetSubExpression(string tagname)
        {
            switch (tagname)
            {
                case "work":
                    return "force*distance";
                default:
                    return null;    // always return null if the tagname is not a sub-expression
            }
        }

 

In a real-world application you would typically be looking up tag expansions in your application’s configuration database. Whether you choose to cache subexpressions is up to you. FYI, GetSubExpression is called as part of the Parse() method, never during Result evaluation.

To make use of your sub-expression server, you simply pass it into the Parser constructor:


        var parser = new Parser(new TagValueServer(), new SubexpressionServer());

 

Now your users can make use of named, re-usable formulae, if your application UI allows it. A working example that demonstrates sub-expression support is here (link)

Circular reference protection

Sub-expressions can make use of other sub-expressions. They can be "nested" in this way to any practical depth.

Inevitably, when you allow your users to configure their own named expressions, there will occasionally be circular references. For example “foo=baz+bar” and “bar=3.14 x foo”. Any attempt to parse an expression with a circular reference will return an operand with HasError=true and ErrorMessage reading something like ”circular reference involving tag ‘[offending tagname]’”

 

The user's configuration experience

CalcEngine Plus does not mandate any particular user interface to allow users to define sub-expressions. It's up to you as a developer to build a UI that makes sense in the context of your application. In some (rare) circumstances you may not even need to build such a UI, e.g. if all your sub-expressions are defined by administrators in a database or config file.

 

Handling ugly names

Users have to be free to name their entities in a way that makes the most sense to them, but that can pose some difficulties. For example, if there is an entity that holds say, a flow-rate called "total tonnes / day" (and yep, we've seen this!) then you can see the problems that will occur when this participates in an expression:

avgdensity * total tonnes / day

It's utterly ambiguous to the parser! It turns out to be impossible to support these sorts of names without compromising our ability to pick up syntax errors and typos. In general, spaces, mathematical symbols, punctuation and brackets will always pose a problem in mathematical expressions. However, there are a couple of ways to escape them:

1. Square brackets

Example: avgdensity * [total tonnes / day]

The contents within the square brackets are interpreted literally.

Nested square brackets are OK, as long as the number of closing brackets matches the number of open brackets. For example: [item[2]]

If (for some bizarre reason) there is both a tag and a subexpression that matches the content of the square brackets, then it will be interpreted as a the name of a subexpression. If there is no sub-expression by that name, it will be interpreted as a tag name and your ITagValueService will be expected to return a value for it.

2. The tag() function

The tag function also hides its content from the parser. It is subtly different from the square brackets in the following way: The content is always interpreted as a literal tag name - never as the name of a subexpression. This capability is for fairly specialized edge cases - we anticipate that most people would use the square brackets to delimit entity names, as they work for both sub-expression names and tag names.

It's also worth mentioning

3. Custom functions

When you extend CalcEnginePlus with custom functions, you have a lot of control over how each argument to your custom function is interpreted, i.e. as a literal string or a parse-able expression.