Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expression.HasErrors() not flagging error with if function #44

Open
aintJoshinya opened this issue Jun 19, 2020 · 1 comment
Open

Expression.HasErrors() not flagging error with if function #44

aintJoshinya opened this issue Jun 19, 2020 · 1 comment

Comments

@aintJoshinya
Copy link

var expr = new Expression("if (1 = 1, 'office')");
Console.WriteLine($"has error:{expr.HasErrors()}"); //false
Console.WriteLine(expr.Evaluate()); //throws error!

Running the above code with the latest version (2.2.80), the expression will not show as having errors ( the second line), but when evaluating it. it willk throw an argument exception (third line).

Why is this happening? is there a better way to ensure a given expression does not have errors?

@aintJoshinya
Copy link
Author

aintJoshinya commented Sep 30, 2020

We ended up writing a custom visitor that implemented some parameter checking for us. Just in case it helps anyone else, this is roughly what we are using:

//example usage
public List<string> ValidateExpression(string expression)
        {
            var expr = new Expression(expression);
            if (expr.HasErrors())
                return new List<string>() {"Expression has Invalid Syntax"};
            
            var functionParamValidator = new FunctionParamValidator();
            expr.ParsedExpression.Accept(functionParamValidator);
            return functionParamValidator.Errors;
        }


public enum NCalcFunctionParamRequirementType
    {
        Exact,
        Minimum
    }
    class NCalcFunctionParamRequirement
    {
        public NCalcFunctionParamRequirementType Type { get; private set; }
        public int Value { get; private set; }

        public NCalcFunctionParamRequirement(int value, NCalcFunctionParamRequirementType type)
        {
            Value = value;
            Type = type;
        }

        public bool RequirementMet(int parameterCount) =>
            Type switch
            {
                NCalcFunctionParamRequirementType.Exact => parameterCount == Value,
                NCalcFunctionParamRequirementType.Minimum => parameterCount >= Value,
                _ => throw new ArgumentOutOfRangeException("Invalid Enum Value")
            };

    }

    class FunctionParamValidator : LogicalExpressionVisitor
    {
        public List<string> Errors { get; private set; } = new List<string>();

        private readonly Dictionary<string, NCalcFunctionParamRequirement> CustomFunctionParamCount;

        private readonly Dictionary<string, NCalcFunctionParamRequirement> NCalcuFunctionParamCount = new Dictionary<string, NCalcFunctionParamRequirement>()
        {
            {"Abs", new NCalcFunctionParamRequirement(1, NCalcFunctionParamRequirementType.Exact) },
            {"Acos", new NCalcFunctionParamRequirement(1, NCalcFunctionParamRequirementType.Exact) },
            {"Asin", new NCalcFunctionParamRequirement(1, NCalcFunctionParamRequirementType.Exact) },
            {"Atan", new NCalcFunctionParamRequirement(1, NCalcFunctionParamRequirementType.Exact) },
            {"Ceiling", new NCalcFunctionParamRequirement(1, NCalcFunctionParamRequirementType.Exact) },
            {"Cos", new NCalcFunctionParamRequirement(1, NCalcFunctionParamRequirementType.Exact) },
            {"Exp", new NCalcFunctionParamRequirement(1, NCalcFunctionParamRequirementType.Exact) },
            {"Floor", new NCalcFunctionParamRequirement(1, NCalcFunctionParamRequirementType.Exact) },
            {"IEEERemainder", new NCalcFunctionParamRequirement(2, NCalcFunctionParamRequirementType.Exact) },
            {"Log", new NCalcFunctionParamRequirement(2, NCalcFunctionParamRequirementType.Exact) },
            {"Log10", new NCalcFunctionParamRequirement(1, NCalcFunctionParamRequirementType.Exact) },
            {"Pow", new NCalcFunctionParamRequirement(2, NCalcFunctionParamRequirementType.Exact) },
            {"Round", new NCalcFunctionParamRequirement(2, NCalcFunctionParamRequirementType.Exact) },
            {"Sign", new NCalcFunctionParamRequirement(1, NCalcFunctionParamRequirementType.Exact) },
            {"Sin", new NCalcFunctionParamRequirement(1, NCalcFunctionParamRequirementType.Exact) },
            {"Sqrt", new NCalcFunctionParamRequirement(1, NCalcFunctionParamRequirementType.Exact) },
            {"Tan", new NCalcFunctionParamRequirement(1, NCalcFunctionParamRequirementType.Exact) },
            {"Truncate", new NCalcFunctionParamRequirement(1, NCalcFunctionParamRequirementType.Exact) },
            {"Max", new NCalcFunctionParamRequirement(2, NCalcFunctionParamRequirementType.Exact) },
            {"Min", new NCalcFunctionParamRequirement(2, NCalcFunctionParamRequirementType.Exact) },
            {"if",  new NCalcFunctionParamRequirement(3, NCalcFunctionParamRequirementType.Exact) },
            {"in",  new NCalcFunctionParamRequirement(2, NCalcFunctionParamRequirementType.Minimum) }
        };

        public FunctionParamValidator(Dictionary<string, NCalcFunctionParamRequirement> customFunctionParamCount = null)
        {
            CustomFunctionParamCount = customFunctionParamCount ?? new Dictionary<string, NCalcFunctionParamRequirement>();
        }

        public override void Visit(NCalc.Domain.Identifier function)
        {
        }

        public override void Visit(NCalc.Domain.UnaryExpression expression)
        {
            expression.Expression.Accept(this);
        }

        public override void Visit(NCalc.Domain.BinaryExpression expression)
        {
            //Visit left and right
            expression.LeftExpression.Accept(this);
            expression.RightExpression.Accept(this);
        }

        public override void Visit(NCalc.Domain.TernaryExpression expression)
        {
            //Visit left, right and middle
            expression.LeftExpression.Accept(this);
            expression.RightExpression.Accept(this);
            expression.MiddleExpression.Accept(this);
        }

        public override void Visit(Function function)
        {
            if (CustomFunctionParamCount.ContainsKey(function.Identifier.Name))
            {
                ValidateFunctionParamCount(function, CustomFunctionParamCount[function.Identifier.Name]);
            }
            else if (NCalcuFunctionParamCount.ContainsKey(function.Identifier.Name))
            {
                ValidateFunctionParamCount(function, NCalcuFunctionParamCount[function.Identifier.Name]);
            }
            else
            {
                //in this case, its possible a function name in the expression has a typo. It's also possible 
                //the function exists, but we don't have parameter info for it. In either case, I think it would
                //be good to report a validation issue!
                Errors.Add($"No function parameter count info was found for the function '{function.Identifier.Name}'. This may be a typo" +
                    $"or we may need to add additional parameter count data for a custom function for this validation to work!.");
            }

            foreach (var expr in function.Expressions)
            {
                expr.Accept(this);
            }
        }
        public override void Visit(LogicalExpression expression)
        {

        }

        public override void Visit(ValueExpression expression)
        {

        }

        private void ValidateFunctionParamCount(Function function, NCalcFunctionParamRequirement paramRequirement)
        {
            if (!paramRequirement.RequirementMet(function.Expressions.Length))
            {
                var requirementTypeToEnglishPhrase = paramRequirement.Type == NCalcFunctionParamRequirementType.Minimum
                    ? "a minimum of"
                    : "exactly";
                Errors.Add($"function '{function.Identifier.Name}' is expected to have " +
                    $"{requirementTypeToEnglishPhrase} {paramRequirement.Value} parameters," +
                    $" but {function.Expressions.Length} were specified! " +
                    $"Parameters: ({string.Join(",", function.Expressions.Select(x => x.ToString()).ToList())})");
            }
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant