Preli
Preli

Reputation: 3031

Get the parameters in an expression using NCalc

I have an expression which I want to parse to get a list of all used parameters.

For example: "X + 5 / (Y - 1)" should give me the following result: X, Y

I already use NCalc in my project; so is it possible to use NCalc to get the parameters used in an expression?

According to this one discussion entry (https://ncalc.codeplex.com/discussions/361959) it is, but I don't quite understand the answer.

Upvotes: 12

Views: 13189

Answers (5)

Luis Rosa
Luis Rosa

Reputation: 141

Based on Chris answer, this one fixes the stack overflow which can happen for UnaryExpression, and adds it to LogicalExpression which needs it

    internal class ExpressionParameterExtractionVisitor : LogicalExpressionVisitor
    {
        public HashSet<string> Parameters = new HashSet<string>();

        public override void Visit(Identifier function)
        {
            //Parameter - add to list
            Parameters.Add(function.Name);
        }

        public override void Visit(UnaryExpression expression)
        {
            //no need, can cause stack overflow
        }

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

        public override void Visit(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 (function.Expressions != null)
                foreach (var expression in function.Expressions)
                    expression.Accept(this);
        }

        public override void Visit(LogicalExpression expression)
        {
            expression.Accept(this);
        }

        public override void Visit(ValueExpression expression)
        {
            //no need, can cause stack overflow
        }
    }

Upvotes: 0

nemostyle
nemostyle

Reputation: 814

Based on Chris Sinclairs answer, now is very popular NCalcAsync nuget package. In that case, you can have something like this:

class ParameterExtractionVisitor : LogicalExpressionVisitor
{
    public HashSet<string> Parameters = new();

    public override Task VisitAsync(Identifier function)
    {
        //Parameter - add to list
        Parameters.Add(function.Name);
        return Task.CompletedTask;
    }

    public override async Task VisitAsync(UnaryExpression expression)
    {
        await expression.Expression.AcceptAsync(this);
    }

    public override async Task VisitAsync(BinaryExpression expression)
    {
        //Visit left and right
        await expression.LeftExpression.AcceptAsync(this);
        await expression.RightExpression.AcceptAsync(this);
    }

    public override async Task VisitAsync(TernaryExpression expression)
    {
        //Visit left, right and middle
        await expression.LeftExpression.AcceptAsync(this);
        await expression.RightExpression.AcceptAsync(this);
        await expression.MiddleExpression.AcceptAsync(this);
    }

    public override async Task VisitAsync(Function function)
    {
        foreach (var expression in function.Expressions)
        {
            await expression.AcceptAsync(this);
        }
    }

    public override Task VisitAsync(LogicalExpression expression)
    {
        return Task.CompletedTask;
    }

    public override Task VisitAsync(ValueExpression expression)
    {
        return Task.CompletedTask;
    }
}

Upvotes: 0

Chris Sinclair
Chris Sinclair

Reputation: 23208

From the discussion/answer here: http://ncalc.codeplex.com/discussions/360990

A implementation that I've tested and works (for your provided sample expression) is to implement a LogicalExpressionVisitor and have it record the parameters as they are found:

class ParameterExtractionVisitor : LogicalExpressionVisitor
{
    public HashSet<string> Parameters = new HashSet<string>();

    public override void Visit(NCalc.Domain.Identifier function)
    {
        //Parameter - add to list
        Parameters.Add(function.Name);
    }

    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)
    {
        foreach (var expression in function.Expressions)
        {
            expression.Accept(this);
        }
    }

    public override void Visit(LogicalExpression expression)
    {

    }

    public override void Visit(ValueExpression expression)
    {

    }
}

Then you would use it as:

var expression = NCalc.Expression.Compile("2 * [x] ^ 2 + 5 * [y]", false);

ParameterExtractionVisitor visitor = new ParameterExtractionVisitor();
expression.Accept(visitor);

var extractedParameters = visitor.Parameters;

foreach (var param in extractedParameters)
    Console.WriteLine(param);

This outputs "x" and "y" for me.

Note the use of HashSet in the ParameterExtractionVisitor. This is because if your expression contains the same variable more than once (for example: "[x] + [x]") it will be added twice. If you want to store an entry each time the same variable is used, replace the HashSet with a List.


That all said, I have very little experience with NCalc, so my implementation of the overridden methods of LogicalExpressionVisitor are guesses. When I overrode the void Visit(ValueExpression expression) method with expression.Accept(this), it resulted in a StackOverflowException. So I simply left the implementation blank and it seemed to work. So I would suggest that you take my answer here with a very large grain of salt. Your mileage may vary and I can't say if this works for all types of expressions.

Upvotes: 15

Alien Technology
Alien Technology

Reputation: 1838

This works for me. Your mileage may vary.

   public List<string> GetParameters(string expression) {
       List<string> parameters = new List<string>();
       Random random = new Random();
       NCalc.Expression e = new NCalc.Expression(expression);

       e.EvaluateFunction += delegate(string name, NCalc.FunctionArgs args) {
           args.EvaluateParameters();
           args.Result = random.Next(0, 100);
       };
       e.EvaluateParameter += delegate(string name, NCalc.ParameterArgs args) {
           parameters.Add(name);
           args.Result = random.Next(0, 100);
       };
       try {
           e.Evaluate();
           }
       catch {
            }
       return parameters;
    }

ref: https://ncalc.codeplex.com/discussions/79258#editor

Upvotes: 3

Larry
Larry

Reputation: 18031

Here is another approach I use:

I built a NCalc extension method that allows to process parameters and functions on the fly.

internal static class NCalcExtensions
{
    public static object Evaluate(this Expression exp, EvaluateParameterHandler evaluateParameters = null, EvaluateFunctionHandler evaluateFunctions = null)
    {
        try
        {
            if (evaluateParameters != null)
                exp.EvaluateParameter += evaluateParameters;

            if (evaluateFunctions != null)
                exp.EvaluateFunction += evaluateFunctions;

            return exp.Evaluate();
        }
        finally
        {
            exp.EvaluateParameter -= evaluateParameters;
            exp.EvaluateFunction -= evaluateFunctions;
        }
    }
}

Among other things, I can use it to run a dummy evaluation to get parameters and functions names.

var paramNames = new List<string>();
var functionNames = new List<string>();

expression.Evaluate(
    new EvaluateParameterHandler((s, a) =>
    {
        paramNames.Add(s);
        a.Result = 1; // dummy value
    }),
    new EvaluateFunctionHandler((s, a) =>
    {
        functionNames.Add(s);
        a.Result = 1; // dummy value
    }));

Upvotes: 0

Related Questions