Delphi Expression Evaluator - Runtime Formula Parser & Business Rules Engine
What is Delphi Expression Evaluator?
Delphi Expression Evaluator is a free, open-source library for parsing and evaluating mathematical formulas, logical conditions, and string operations at runtime. Written in pure Object Pascal with no external dependencies, it’s perfect for:
- Business Rules Engines - Store rules in config files or databases
- Dynamic Pricing - Calculate discounts, taxes, shipping based on formulas
- Spreadsheet-like Applications - Let users define their own formulas
- Form Validation - Complex validation rules without code changes
- Configuration-Driven Calculations - Change calculations without recompiling
Quick Answer: If you need to evaluate user-defined expressions like
"price * quantity * (1 + taxRate)"at runtime in Delphi, this library provides a safe, fast, and feature-rich solution with short-circuit evaluation, pattern matching, 30+ built-in functions, and custom function support.
Key Features at a Glance
| Feature | Description |
|---|---|
| Mathematical Operations | +, -, *, /, ^, mod, div with correct precedence |
| Short-Circuit Evaluation | and/or skip right operand when result is determined |
| LIKE Pattern Matching | SQL-style wildcards: * (any chars), ? (single char) |
| 30+ Built-in Functions | Math, string, date/time, aggregation |
| Variables | Runtime assignment with := or SetVar() |
| Custom Functions | Register your own with lambda syntax |
| If-Then-Else | Conditional expressions inline |
| Interface-Based API | Automatic memory management, no leaks |
| Zero Dependencies | Pure Delphi, no DLLs |
| Free Commercial Use | Apache 2.0 license |
Quick Start: Evaluate Your First Expression
uses ExprEvaluator;
var
Eval: IExprEvaluator;
Result: Variant;
begin
// Create evaluator - automatic memory management via interface
Eval := CreateExprEvaluator;
// Simple arithmetic (respects operator precedence)
Result := Eval.Evaluate('2 + 3 * 4'); // 14 (not 20!)
Result := Eval.Evaluate('power(2, 10)'); // 1024
// Variables
Eval.SetVar('price', 100);
Eval.SetVar('quantity', 5);
Result := Eval.Evaluate('price * quantity'); // 500
// Conditionals
Result := Eval.Evaluate('if price > 50 then "expensive" else "cheap"');
// Pattern matching
Result := Eval.Evaluate('"report.pdf" like "*.pdf"'); // True
end;
// No cleanup needed - interface handles memory
That’s it! The evaluator parses the expression, handles operator precedence correctly, and returns the result.
Short-Circuit Evaluation: Performance & Safety
The evaluator implements true short-circuit evaluation for logical operators - a critical feature for both performance and safety:
// AND: if left is false, right is NEVER evaluated
Eval.Evaluate('false and ExpensiveFunction()'); // ExpensiveFunction not called!
// OR: if left is true, right is NEVER evaluated
Eval.Evaluate('true or ExpensiveFunction()'); // ExpensiveFunction not called!
Why This Matters
- Performance: Skip expensive calculations when result is already determined
- Safety: Avoid division by zero and null reference errors
- Side Effects: Control when functions with side effects execute
// Safe null/zero check pattern
Eval.Evaluate('x <> 0 and (100 / x) > 10'); // Division only if x <> 0
// Conditional function execution
Eval.Evaluate('IsConnected() and SendData()'); // SendData only if connected
LIKE Pattern Matching: SQL-Style Wildcards
The LIKE operator provides powerful pattern matching:
| Wildcard | Matches | Example |
|---|---|---|
* |
Zero or more characters | "hello" like "hel*" → True |
? |
Exactly one character | "hello" like "h?llo" → True |
Pattern Matching Examples
// File extension matching
Eval.Evaluate('"document.pdf" like "*.pdf"'); // True
Eval.Evaluate('"image.jpg" like "*.png"'); // False
// Prefix/suffix matching
Eval.Evaluate('"report_2024.xlsx" like "report_*"'); // True
// Single character wildcard
Eval.Evaluate('"file1.txt" like "file?.txt"'); // True
Eval.Evaluate('"file10.txt" like "file?.txt"'); // False (? = 1 char only)
// Email validation
Eval.Evaluate('"user@domain.com" like "*@*.*"'); // True
// With variables
Eval.SetVar('filename', 'invoice_001.pdf');
Eval.Evaluate('filename like "invoice_*.pdf"'); // True
// Combined with logic
Eval.Evaluate('"test.txt" like "*.txt" or "test.txt" like "*.csv"');
Built-in Functions Reference
Mathematical Functions
| Function | Description | Example |
|---|---|---|
sqrt(x) |
Square root | sqrt(16) → 4 |
abs(x) |
Absolute value | abs(-5) → 5 |
floor(x) |
Round down | floor(3.7) → 3 |
ceil(x) |
Round up | ceil(3.2) → 4 |
round(x, n) |
Round to n decimals | round(3.14159, 2) → 3.14 |
power(x, y) |
Exponentiation | power(2, 8) → 256 |
log(x) |
Log base 10 | log(100) → 2 |
logn(x) |
Natural log | logn(2.718) → ~1 |
Min(...) |
Minimum value | Min(10, 5, 8) → 5 |
Max(...) |
Maximum value | Max(10, 5, 8) → 10 |
String Functions
| Function | Description | Example |
|---|---|---|
Length(s) |
String length | Length("hello") → 5 |
Upper(s) |
Uppercase | Upper("hello") → “HELLO” |
Lower(s) |
Lowercase | Lower("HELLO") → “hello” |
Trim(s) |
Remove whitespace | Trim(" hi ") → “hi” |
Left(s, n) |
First n chars | Left("hello", 2) → “he” |
Right(s, n) |
Last n chars | Right("hello", 2) → “lo” |
Substr(s, start, len) |
Substring | Substr("hello", 2, 3) → “ell” |
IndexOf(needle, s) |
Find position | IndexOf("l", "hello") → 3 |
Replace(s, old, new) |
Replace text | Replace("hello", "l", "L") → “heLLo” |
contains(needle, s) |
Contains check | contains("ell", "hello") → True |
Date/Time Functions
| Function | Description | Example |
|---|---|---|
Now() |
Current date/time | Current timestamp |
Today() |
Current date | Today at midnight |
Year(d) |
Extract year | Year(Today()) → 2024 |
Month(d) |
Extract month | Month(Today()) → 12 |
Day(d) |
Extract day | Day(Today()) → 11 |
ParseDate(s) |
Parse YYYY-MM-DD | ParseDate("2024-01-15") |
FormatDate(d, fmt) |
Format date | FormatDate(Today(), "dd/mm/yyyy") |
DateAdd(d, n, unit) |
Add to date | DateAdd(Today(), 7, "day") |
DateDiff(d1, d2, unit) |
Difference | DateDiff(start, end, "day") |
Variables: Store and Reuse Values
Variables persist across evaluations and are case-insensitive:
// Method 1: Assignment operator
Eval.Evaluate('x := 10');
Eval.Evaluate('y := 20');
Eval.Evaluate('x + y'); // 30
// Method 2: SetVar (recommended for external data)
Eval.SetVar('price', 99.99);
Eval.SetVar('taxRate', 0.21);
Eval.Evaluate('price * (1 + taxRate)'); // 120.99
// Variables work in complex expressions
Eval.SetVar('items', 5);
Eval.SetVar('discount', 0.1);
Eval.Evaluate('price * items * (1 - discount)'); // 449.96
Custom Functions: Extend with Your Logic
Register your own functions using lambda syntax:
// Simple function
Eval.RegisterFunction('cube',
function(const Args: array of Variant): Variant
begin
Result := Power(Args[0], 3);
end);
Eval.Evaluate('cube(3)'); // 27
// Function with validation
Eval.RegisterFunction('clamp',
function(const Args: array of Variant): Variant
begin
var Value := Double(Args[0]);
var MinVal := Double(Args[1]);
var MaxVal := Double(Args[2]);
if Value < MinVal then Result := MinVal
else if Value > MaxVal then Result := MaxVal
else Result := Value;
end);
Eval.Evaluate('clamp(150, 0, 100)'); // 100
// Business logic
Eval.RegisterFunction('calculateTax',
function(const Args: array of Variant): Variant
begin
var Amount := Double(Args[0]);
var Country := string(Args[1]);
case Country of
'IT': Result := Amount * 0.22;
'DE': Result := Amount * 0.19;
'US': Result := Amount * 0.08;
else
Result := Amount * 0.15;
end;
end);
Eval.Evaluate('calculateTax(1000, "IT")'); // 220
Operator Precedence (Lowest to Highest)
| Level | Operators | Description |
|---|---|---|
| 1 | := |
Assignment |
| 2 | if-then-else |
Conditional |
| 3 | or, xor |
Logical OR/XOR |
| 4 | and |
Logical AND |
| 5 | =, <>, <, <=, >, >=, like |
Comparison |
| 6 | +, - |
Addition, Subtraction |
| 7 | *, /, mod, div |
Multiplication, Division |
| 8 | not, - (unary) |
Unary operators |
| 9 | ^ |
Exponentiation |
| 10 | () |
Parentheses |
Real-World Use Cases
Dynamic Pricing Engine
// Formula stored in database/config
var PricingFormula := Config.ReadString('Pricing', 'DiscountFormula',
'basePrice * quantity * (if customerType = "premium" then 0.85 else if quantity > 5 then 0.90 else 1.0)');
Eval.SetVar('basePrice', 100);
Eval.SetVar('quantity', 10);
Eval.SetVar('customerType', 'premium');
var FinalPrice := Eval.Evaluate(PricingFormula); // 850
Business Rules Validation
// Rules stored externally, changeable without recompilation
var ApprovalRule := 'amount <= 1000 or (amount <= 5000 and managerApproved) or directorApproved';
Eval.SetVar('amount', 3500);
Eval.SetVar('managerApproved', True);
Eval.SetVar('directorApproved', False);
if Eval.Evaluate(ApprovalRule) then
ProcessOrder
else
RequireAdditionalApproval;
Form Validation
Eval.SetVar('email', UserEmailInput);
Eval.SetVar('age', UserAgeInput);
var IsValid := Eval.Evaluate('
email like "*@*.*" and
Length(email) >= 5 and
age >= 18 and age <= 120
');
Configuration-Driven Shipping
var ShippingFormula := Config.ReadString('Shipping', 'CostFormula',
'if weight <= 1 then 5.99 else if weight <= 5 then 9.99 else weight * 2.50');
Eval.SetVar('weight', PackageWeight);
var ShippingCost := Eval.Evaluate(ShippingFormula);
API Reference
IExprEvaluator Interface
| Method | Description |
|---|---|
Evaluate(Expression): Variant |
Parse and evaluate expression |
SetVar(Name, Value) |
Set variable value |
GetVar(Name): Variant |
Get variable value |
RegisterFunction(Name, Func) |
Register custom function |
ClearVars |
Remove all variables |
Factory Function
function CreateExprEvaluator: IExprEvaluator;
Creates a new evaluator instance with automatic memory management.
Frequently Asked Questions
What Delphi version is required?
Delphi 10 Seattle or later. Works with all subsequent versions including the latest releases.
Is it thread-safe?
Each IExprEvaluator instance maintains its own state (variables, functions). For multi-threaded applications, create separate instances per thread.
How do I handle errors?
The evaluator raises Exception with descriptive messages. Wrap Evaluate() in try-except:
try
Result := Eval.Evaluate(UserFormula);
except
on E: Exception do
ShowError('Invalid formula: ' + E.Message);
end;
What’s the performance like?
The recursive descent parser has O(n) complexity. Typical expressions evaluate in microseconds. For high-frequency evaluation, reuse the evaluator instance.
Can I use this in commercial projects?
Yes! Apache 2.0 license allows commercial use without restrictions.
Does it work on mobile (iOS/Android)?
Yes, pure Delphi code works on all platforms supported by Delphi: Windows, macOS, Linux, iOS, Android.
Installation
- Download from GitHub
- Add source folder to your Library Path
- Include
uses ExprEvaluator;in your unit - Create evaluator:
Eval := CreateExprEvaluator; - Evaluate!
Result := Eval.Evaluate('2 + 2');
Requirements
- Delphi: 10 Seattle or later
- Dependencies: None
- Platforms: Windows, macOS, Linux, iOS, Android
Related Open Source Projects
Delphi Expression Evaluator integrates well with other open source projects by the same author:
| Project | Description | Integration |
|---|---|---|
| DMVCFramework | REST API framework for Delphi | Use expressions for dynamic business rules in API endpoints, conditional responses, and calculated fields |
| DelphiGuard | RAII memory management | Automatic cleanup for evaluator instances (though interface-based API already handles this) |
| Delphi Fake Data Utils | Test data generator | Generate test data for expression variables and validation rules |
Links
- GitHub: github.com/danieleteti/delphi-expressions-evaluator
- Support: DelphiMVCFramework Facebook Group
Delphi Expression Evaluator - Runtime formula parsing made simple. Free and open-source under Apache 2.0 license.
Comments
comments powered by Disqus