nreco / lambdaparser

Runtime parser for string expressions (formulas, method calls). Builds dynamic LINQ expression tree and compiles it to lambda delegate.
http://www.nrecosite.com/
MIT License
309 stars 55 forks source link

Expressions using "not" not always work #35

Closed jmpardo closed 3 years ago

jmpardo commented 3 years ago

Hello

I´ve found that the parser raises an exception in cases like this: not ( foo = bar) anyway **not** works as expected in expressions like ((foo and foo1 = bar1 ) or not (foo2 = bar2)) by replacing **not** by ! you get the expression working always.

I think this could be maybe an edge case of the issue https://github.com/nreco/lambdaparser/issues/30

Thanks for the good job.

Jose

VitaliyMF commented 3 years ago

@jmpardo in you expressions "not" is a function that is defined as in #30?

I need to know how to reproduce the issue. What are values for 'foo' and 'bar'? Ideally if you can provide a complete code snippet that illustrates the issue.

jmpardo commented 3 years ago

Sure @VitaliyMF , here you are

using System.Collections.Generic;
using NReco.Linq;
using Xunit;

namespace Sample.TestEval
{
    public class LambdaParserTests
    {
        private readonly LambdaParser _lambdaParser;
        private readonly Dictionary<string, object> _varContext;

        public LambdaParserTests()
        {
            _lambdaParser = new LambdaParser
            {
                UseCache = true,
                AllowSingleEqualSign = true
            };

            _varContext = new Dictionary<string, object>
            {
                ["foo"] = true,
                ["foo1"] = "a",
                ["bar1"] = "a",
                ["foo2"] = "b",
                ["bar2"] = "C"
            };
        }

        [Fact]
        public void Eval_When_Using_Not_Throws_Exception()
        {
            var ev1 = (bool)_lambdaParser.Eval(@"not(foo1 = bar1)", _varContext);

            Assert.False(ev1);
        }

        [Fact]
        public void Eval_When_Using_Not_Exclamation_Works()
        {
            var ev1 = (bool)_lambdaParser.Eval(@"!(foo1 = bar1)", _varContext);

            Assert.False(ev1);
        }

        [Fact]
        public void Eval_When_Using_Not_In_Complex_Expressions_Works()
        {
            var ev1 = (bool)_lambdaParser.Eval(@"((foo and foo1 = bar1 ) or not (foo2 = bar2))", _varContext);

            Assert.True(ev1);
        }
    }
}

Thanks for your quick response,

VitaliyMF commented 3 years ago

@jmpardo LambdaParser works correctly in case of Eval_When_Using_Not_In_Complex_Expressions_Works -- let me explain why.

Parser doesn't use variable's context when expression is parsed - this means that all variables are 'resolved' in run-time, not during expression compilation (to the delegate). This also means that if variable is not defined ('not' in your case) exception will occur only when this part of expression tree is executed -- but on case of this expression:

((foo and foo1 = bar1 ) or not (foo2 = bar2))

and this set of variables:

            _varContext = new Dictionary<string, object>
            {
                ["foo"] = true,
                ["foo1"] = "a",
                ["bar1"] = "a",
                ["foo2"] = "b",
                ["bar2"] = "C"
            };

not (foo2 = bar2) is never executed because (foo and foo1 = bar1 ) is true (this is standard optimization of the logical conditions -- it is performed by the .NET Expression implementation). To get an exception it is enough to change, say, "bar1" to "b", in this case 2nd part of "or" will be executed and since "not" is not defined exception is thrown.

jmpardo commented 3 years ago

Oh, I see. My bad, I thougth "not" was supported by default as "and" and "or". Implementing "not" as a function and adding that to the context all test pass. Thanks!