Reputation: 3031
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
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
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
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
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
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