sklose/NCalc2

Check "if" expression for errors such as undefined formulas.

fdahlberg opened this issue · 8 comments

Hi,

I have an expression of the following type:
if(a > 3, MyFunction(34), 45)
The problem is that MyFunction doesn't exist and I would like to know this somehow. Is that possible? If I evaluate the formula with a > 3 I will get an exception of course. Problem is that i don't know what the formulas will be like and I can't test all different input combinations.

As far as I understand HasErrors only checks that the syntax is correct, not that the functions actually are implemented.

Kind Regards
Fredrik Dahlberg

I think there are two answers to this:

1.) Using NCalc in interpreted mode
Here you currently don't get that check because functions are evaluated with an event firing back into the code of the consumer of this library - and this only happens when the expression is evaluated. I guess this could be extended with some sort of validation event firing once after parsing that could report back any unimplemented functions (feel free to submit a PR for that) :).

2.) Using NCalc in compiled mode
Here you should get some sort of error because during compilation it won't be able find the function on the context type and it will probably throw a reflection exception stating that the method wasn't found.

Pardon my ignorance, but I'm not sure what you mean with interpreted and compiled mode. I thought it was always interpreted in in .Net.

I cloned the project to see if I could make a contribution. I'm guessing I need .net Core 2.0 for this?

this library can evaluate your expressions in two different modes:

  1. interpreted: your expression is parsed into a syntax tree and on evaluation that tree is traversed to compute the result
  2. compiled: your expression is converted to IL/byte code and directly executed by the .NET runtime

the 2. mode is much faster (see benchmarks in README.md)

Could you give examples of those ways of evaluating?
I'm using somehting like this:

NCalc.Expression expression = new NCalc.Expression(formula);
expression.EvaluateFunction += NCalcExtensionFunctions; // adding my own functions
expression.Parameters = parameters; // adding my parameters
var evaluationResult = expression.Evaluate(); 

Is that interpreted or compiled mode?

that is interpreted mode. for compiled/lambda mode see the README.md for an example.

So compiled/lambda mode is faster but it seems a bit less intuitive. Are there any drawbacks to the lambda mode? Can i still use it when the formula and parameters are determined at run time?

You can still use it, but:

  • every time the formula changes, you have to recompile (only a problem if you do this 10,000ths of times, cause the CLR won't release old lambdas) ... with the interpreter you have to re-parse, but that is cleaned up but he garbage collector eventually
  • parameters are properties on your context object, you can change them for each evaluation run ... README.md has some examples for that
  • you will have to define all functions except for "if" and "in" on your context object

I will quite likely create the formulas 10 000:s of times, so I guess I'll have to stick with the interpreted mode.

I have a function that looks like this, that might called loads of times:

        public static FormulaResult EvaluateFormula(string formula, Dictionary<string, object> parameters)
        {
            var result = new FormulaResult();
            if (string.IsNullOrWhiteSpace(formula))
            {
                return result;
            }

            NCalc.Expression expression = new NCalc.Expression(formula);

            expression.EvaluateFunction += NCalcExtensionFunctions;
            expression.Parameters = parameters;

            try
            {
                if (expression.HasErrors())
                {
                    result.ErrorMessage = expression.Error;
                }
                else
                {

                    var evaluationResult = expression.Evaluate();
                    if (evaluationResult is int)
                    {
                        result.Value = (int)evaluationResult;
                    }
                    else if (evaluationResult is double)
                    {
                        result.Value = (double)evaluationResult;
                    }
                    else
                    {
                        result.ErrorMessage = "Formula must evaluate to a number!";
                    }
                }
            }
            catch (Exception exception)
            {
                result.ErrorMessage = exception.Message;
            }

            return result;
        }