A simple interpreter for evaluating custom expressions and rules; Perfect for when clients want to define business rules or aggregation logic on the fly.
License
—
Deps
1
Install Size
—
Vulns
✓ 0
Published
May 22, 2025
$ dotnet add package Simple.InterpreterSimple.Interpreter is a lightweight C# library providing a straightforward interpreter for a simple, custom expression language. This NuGet package enables developers to empower their customers or clients to define dynamic expressions for various purposes, such as business rules, conditional logic, and data filtering, without requiring code recompilation. Notably, as demonstrated in the unit tests, the interpreter can also handle complex objects as variables within the expression context.
==, !=, >, <, >=, <=). Version 8.2.0 added support for more natural language comparison operators including:
equal tois equal tonot equal tois not equal togreater thanis greater thanless thanis less thangreater than or equal tois greater than or equal toless than or equal tois less than or equal toand) and OR (or) operations for building complex conditions.in (array contains), not in (array does not contain) to check against arrays.IEnumerable to allow for easy filtering and projection of collections using expressions.You can install the Simple.Interpreter NuGet package using the NuGet Package Manager in Visual Studio or the .NET CLI:
dotnet add package Simple.Interpreter
using Simple.Interpreter;
using System;
using System.Collections.Generic;
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string City { get; set; }
public string SayHi(string to)
{
return $"Hi there {to}!, I'm {Name}";
}
}
// Create an instance of the interpreter
var interpreter = new ExpressionInterpreter();
// Define the expression accessing properties of a complex object
string expressionString = "user.Age > 18 and user.City == 'Johannesburg'";
// Create a complex object to use as a variable
var user = new User
{
Name = "Alice",
Age = 25,
City = "Pretoria"
};
// Provide the context (including the complex object) for the expression
var context = new Dictionary<string, object>
{
{"user", user}
};
// Parse the expression
var expression = interpreter.GetExpression(expressionString);
// Set its scope
expression.SetScope(context);
// Evaluate the expression
bool isAdultInJohannesburg = expression.Evaluate<bool>();
if (isAdultInJohannesburg)
{
Console.WriteLine($"{user.Name} meets the criteria.");
}
else
{
Console.WriteLine($"{user.Name} does not meet the criteria.");
}
using Simple.Interpreter;
using System;
using System.Collections.Generic;
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string City { get; set; }
public string SayHi(string to)
{
return $"Hi there {to}!, I'm {Name}";
}
}
// Create an instance of the interpreter
var interpreter = new ExpressionInterpreter();
// Define the expression accessing properties of a complex object
string expressionString = "alice.SayHi('Frank') if(alice.Age > 18+51) else bob.SayHi('Frank')";
var alice = new User
{
Name = "Alice",
Age = 25,
City = "Pretoria"
};
var bob = new User
{
Name = "Bob",
Age = 19,
City = "Cape Town"
};
// Provide the context (including the complex object) for the expression
var context = new Dictionary<string, object>
{
{"alice", alice},
{"bob", bob}
};
// Parse the expression
var expression = interpreter.GetExpression(expressionString);
// Set its scope
expression.SetScope(context);
// Evaluate the expression
object result = expression.Evaluate();
Console.WriteLine($"{result}"); //Outputs: Hi there Frank!, I'm Bob
For more see the Simple.Interpreter.Demo project
// Create an instance of the interpreter
var interpreter = new ExpressionInterpreter();
// Define the expression
string expressionString = "user.Age > 18 and user.City == 'Johannesburg'";
// Define valid variable types allowed in expressions
var validVariableTypes = new Dictionary<string, Type>
{
{ "user", typeof(User) },
};
// validate the expression
var isValid = interpreter.Validate(expressionString, validVariableTypes, out var errors); //Returns true
if (isValid)
{
Console.WriteLine($"The expression is valid!");
}
else
{
Console.WriteLine($"There were issues with the expression: ");
foreach (var err in errors)
{
Console.WriteLine(err.Message);
}
}
For more see the Simple.Interpreter.Demo project
Custom functions can be defined and used by Expressions created with the ExpressionInterpreter.
Custom functions can accept up to 4 arguments.
using Simple.Interpreter;
using System;
using System.Collections.Generic;
private static bool IsUserOlderThan(User user, int age)
{
bool result = user?.Age > age;
return result;
}
ExpressionInterpreter interpreter = new ExpressionInterpreter();
//Register custom Function
interpreter.RegisterFunction("isUserOlderThan", IsUserOlderThan);
var frank = new User
{
Name = "Frank",
Age = 40,
City = "Cape Town"
};
var ageToTest = 30;
var scope = new Dictionary<string, object>()
{
{"user", frank },
{"age", ageToTest }
};
string expressionString = "isUserOlderThan(user, age)";
Expression expression = interpreter.GetExpression(expressionString);
// Set its scope
expression.SetScope(scope);
bool isOldEnough = expression.Evaluate<bool>(); //Returns true
if(result is bool isOldEnough && isOldEnough)
{
Console.WriteLine($"{frank} is Older than {ageToTest}");
}
else
{
Console.WriteLine($"{frank} is not older than {ageToTest}");
}
For more see the Simple.Interpreter.Demo project
The Expression evaluation process offers detailed step-by-step logging to aid developers in understanding and debugging complex expressions. This logging is performed at the DEBUG level and provides insight into variable resolution, function calls, operator applications, and intermediate results. To enable this verbose logging, ensure your application's logging configuration is set to DEBUG for the relevant logger. The ExpressionInterpreter leverages ILoggerFactory to create its internal logger; you can either provide an ILoggerFactory instance upon ExpressionInterpreter initialization, or if using dependency injection (DI), it will be automatically injected. For fine-grained control, you can also explicitly toggle this debugging output for a specific Expression instance using the .WithDebugging(false|true) method.
ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Debug);
});
// Create an instance of the interpreter
var interpreter = new ExpressionInterpreter(loggerFactory);
// Define the expression
string expressionString = "user.Age > 18 and user.City == 'Johannesburg'";
var user = new User
{
Name = "Alice",
Age = 25,
City = "Pretoria"
};
var expression = interpreter.GetExpression(expressionString);
expression.SetScopedVariable("user", alice);
expression.Evaluate();
Logging output:
dbug: Simple.Interpreter.Ast.Expression[0]
Evaluating Expression: user.Age > 18 and user.City == "Johannesburg"...
dbug: Simple.Interpreter.Ast.Expression[0]
Evaluating Binary Node: user.Age > 18 and user.City == "Johannesburg"...
dbug: Simple.Interpreter.Ast.Expression[0]
Evaluating Binary Node: user.Age > 18...
dbug: Simple.Interpreter.Ast.Expression[0]
Evaluating Member Node: user.Age...
dbug: Simple.Interpreter.Ast.Expression[0]
Member Evaluated: 25
dbug: Simple.Interpreter.Ast.Expression[0]
Evaluating: 25 > 18...
dbug: Simple.Interpreter.Ast.Expression[0]
Evaluated: True
dbug: Simple.Interpreter.Ast.Expression[0]
Evaluating Binary Node: user.City == "Johannesburg"...
dbug: Simple.Interpreter.Ast.Expression[0]
Evaluating Member Node: user.City...
dbug: Simple.Interpreter.Ast.Expression[0]
Member Evaluated: Pretoria
dbug: Simple.Interpreter.Ast.Expression[0]
Evaluating: Pretoria == Johannesburg...
dbug: Simple.Interpreter.Ast.Expression[0]
Evaluated: False
dbug: Simple.Interpreter.Ast.Expression[0]
Evaluating: True and False...
dbug: Simple.Interpreter.Ast.Expression[0]
Evaluated: False
dbug: Simple.Interpreter.Ast.Expression[0]
Expression Evaluated: False
age, productName, user). These will be resolved from the provided context. You can access properties, fields and methods of complex object variables using dot notation (e.g., user.Age, order.Name, user.DoSomething()). The expression can only access variables 1 level deep (e.g. user.FullAddress.PostalCode is 2 level deep and will fail validation)10, 3.14).'Hello').true or false.ExpressionInterpreter can parse array literals of string, double and int (e.g. [1, 2, 3, 4, 5] or ['a', 'b', 'c']).+ (addition), - (subtraction), * (multiplication), / (division).== (equals), != (not equals), > (greater than), < (less than), >= (greater than or equal to), <= (less than or equal to).and (logical AND), or (logical OR).in (array contains), not in (array does not contain).(a + b) * c).startsWith(name, 'Prefix'), isOlderThan(currentUser, 30)). By default, the ExpressionInterpreter includes a set of built-in functions including:
startsWith => Equivalent to string.StartsWithendsWith => Equivalent to string.EndsWithmin => Equivalent to Math.Min (if numbers are passed as arguments) or returns the shortest string if strings are passed as arguments.max => Equivalent to Math.Max (if numbers are passed as arguments) or returns the longest string if strings are passed as arguments.string => Equivalent to Object.ToString().length => Equivalent to string.Length.'Over 21' if (user.Age > 21) else 'Under 21').Contributions to this project are welcome! If you have ideas for improvements, bug fixes, or new features, especially those enhancing the expression language or the interpreter's ability to handle complex data structures, please feel free to:
git checkout -b feature/your-feature).git commit -am 'Add some feature').git push origin feature/your-feature).This project is licensed under the MIT License. See the LICENSE file for more details.
If you find this project useful, please consider making a donation to support its development. Your support will help me continue to maintain and improve this project.