sklose / NCalc2

expression evaluator for .NET with built-in compiler
MIT License
169 stars 58 forks source link

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

Open fdahlberg opened 6 years ago

fdahlberg commented 6 years ago

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

sklose commented 6 years ago

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.

fdahlberg commented 6 years ago

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?

sklose commented 6 years ago

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)

fdahlberg commented 6 years ago

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?

sklose commented 6 years ago

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

fdahlberg commented 6 years ago

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?

sklose commented 6 years ago

You can still use it, but:

fdahlberg commented 6 years ago

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;
        }