Expression.HasErrors() not flagging error with if function
aintJoshinya opened this issue · 1 comments
aintJoshinya commented
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 commented
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())})");
}
}
}