Compile and evaluate mathematical expressions with C#

Today I will be talking about how you can evaluate mathematical expressions and functions using C# and more specifically using a library called JustFunctional.

What is Just Functional

Just Functional is a .NET evaluator, its built and maintained by me. It has a very opinionated design and a user-friendly API that allows you to customize certain things like operators, constants or take control of some aspect like disabling variables entirely.

I don’t want to make a long post and we won’t cover all the things you can do with this library; you can have a look at link to the documentation above get more details.

What are we doing then?

We are going to be build a console based application that ask the user for a input function like: f(x,y) = X+Y and  for the corresponding values E.g. X=3 & Y=4. Then with this information then we are going to parse and try to evaluate the function. You can find the entire source code here if you don’t want to follow along.

The Code

Without any further delay let’s use the library with this illustrative example. I’m going to assume you have an empty console application and I will be using .NET 6 in this case just because it requires less boilerplate code due to features like implicit usings and top-level statements but you can any other version of .NET.

The first step would be installing the nuget package, its named JustFunctional.Core, one way to do this is by using the dotnet cli, go ahead browse to folder where the .csproj is located, open a terminal and type the following command:

dotnet add package JustFunctional.Core --version 2.0.2

Then let’s jump straight to using the library I will explain everything afterwards:

using JustFunctional.Core;

string expression = AskAndGetInputExpression();
Dictionary<string, decimal> variablesWithValues = AskAndGetInputVariablesWithValues();


var functionFactory = FunctionFactoryBuilder.ConfigureFactory(options =>
{
    options
        .WithEvaluationContextVariablesProvider()
        .WithCompiledEvaluator();
});

var variables = new PredefinedVariablesProvider(variablesWithValues.Keys.ToArray());
TryCreateFunctionResult createResult = functionFactory.TryCreate(expression, variables);

if (!createResult.Success)
{
    Console.WriteLine($"The expression could not be parsed because: {string.Join(", ", createResult.Errors)}");
    return;
}
var evalutaionContext = new EvaluationContext(variablesWithValues);
var result = await createResult.Function.EvaluateAsync(evalutaionContext);

Console.WriteLine(result);
Console.ReadLine();
Program.cs

The first two lines you can see the calls to AskAndGetInputExpression() and AskAndGetInputVariablesWithValues() methods, they simply read the expression and the variables with values respectively (Ex X=3;Y=4) from the console and I will include them for completeness at the end since is just string manipulation.

In the next statement we create a FunctionFactory, although you can create a function directly, if there is some kind of syntax error on the expression you will get exceptions, so when you need features like syntax validation or customizing certain components is a good idea to use the FunctionFactoryBuilder.ConfigureFactory(...) method.

You sure noticed two other statements while configuring the factory. The first one, WithEvaluationContextVariablesProvider() means that you will defer passing variables (or variables and values) whenever you are evaluating/validating. The other, WithCompiledEvaluator() tells the factory to compile function and expression once rather than doing it every time you evaluate. You could have omitted both of them since these are the default values, I decided to include them because this is the way you can change how the library behaves.

The next line functionFactory.TryCreate(expression, variables) will try to parse the expression and will yield an object with a Boolean property called Success. When the parsing phase fails (Success = false) you will find error description in the Errors property.

Once you make sure that expression is parsed correctly you can use Evaluate() or EvaluateAsync() passing the variables along with their corresponding values to get the result of evaluating the function.

Boilerplate Code

Finally, for completeness here are the AskAndGetInputExpression() and AskAndGetInputVariablesWithValues() methods that take care of reading the user input:


string AskAndGetInputExpression()
{
    Console.WriteLine("Enter the expression to evaluate: (E.g. X+Y)");
    var expression = Console.ReadLine() ?? throw new Exception("You need to suply a value");
    return expression;
}

Dictionary<string, decimal> AskAndGetInputVariablesWithValues()
{
    Console.WriteLine("Enter the expression's variables: (E.g. X=3;Y=4)");
    var splittedVariables = Console.ReadLine()?
        .Split(';', StringSplitOptions.RemoveEmptyEntries) ?? new string[0];
    var variablesWithValues = splittedVariables
        .Select(x => x.Split('=', StringSplitOptions.RemoveEmptyEntries))
        .ToDictionary(x => x.First(), v => decimal.Parse(v.Last()));
    
    return variablesWithValues;
}

Summary

JustFunctional is a .Net evaluator that with a few lines of codes allows you to compile and evaluate mathematical expressions and functions within your application, sure there are more robust libraries out there and with many more features but if all you need is some simple math evaluator you can give it a try.

In this post I tried to cover a happy path for Just Functional, but there are more things you can do with it, make sure visit the docs to learn more or have a look at example code here.

Lastly your feedback is appreciated you can certainly open a bug, a feature request or make a contribution by using the library's repository.

Bye!