Become a member!

Delphi Expression Evaluator - Formula & Business Rules

Delphi Expression Evaluator Logo

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

FeatureDescription
Mathematical Operations+, -, *, /, ^, mod, div with correct precedence
Short-Circuit Evaluationand/or skip right operand when result is determined
LIKE Pattern MatchingSQL-style wildcards: * (any chars), ? (single char)
30+ Built-in FunctionsMath, string, date/time, aggregation
VariablesRuntime assignment with := or SetVar()
Custom FunctionsRegister your own with lambda syntax
If-Then-ElseConditional expressions inline
Interface-Based APIAutomatic memory management, no leaks
Zero DependenciesPure Delphi, no DLLs
Free Commercial UseApache 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

  1. Performance: Skip expensive calculations when result is already determined
  2. Safety: Avoid division by zero and null reference errors
  3. 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:

WildcardMatchesExample
*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

FunctionDescriptionExample
sqrt(x)Square rootsqrt(16) → 4
abs(x)Absolute valueabs(-5) → 5
floor(x)Round downfloor(3.7) → 3
ceil(x)Round upceil(3.2) → 4
round(x, n)Round to n decimalsround(3.14159, 2) → 3.14
power(x, y)Exponentiationpower(2, 8) → 256
log(x)Log base 10log(100) → 2
logn(x)Natural loglogn(2.718) → ~1
Min(...)Minimum valueMin(10, 5, 8) → 5
Max(...)Maximum valueMax(10, 5, 8) → 10
Sort(...)Sort values ascendingSort(3, 1, 2) → 1, 2, 3

Conversion Functions

FunctionDescriptionExample
ToString(x)Convert to stringToString(42) → “42”
ToInteger(x)Convert to integerToInteger("42") → 42
ToFloat(x)Convert to floatToFloat("3.14") → 3.14

String Functions

FunctionDescriptionExample
Length(s)String lengthLength("hello") → 5
Upper(s)UppercaseUpper("hello") → “HELLO”
Lower(s)LowercaseLower("HELLO") → “hello”
Trim(s)Remove whitespaceTrim(" hi ") → “hi”
Left(s, n)First n charsLeft("hello", 2) → “he”
Right(s, n)Last n charsRight("hello", 2) → “lo”
Substr(s, start, len)SubstringSubstr("hello", 2, 3) → “ell”
IndexOf(needle, s)Find positionIndexOf("l", "hello") → 3
Replace(s, old, new)Replace textReplace("hello", "l", "L") → “heLLo”
contains(needle, s)Contains checkcontains("ell", "hello") → True

Date/Time Functions

FunctionDescriptionExample
Now()Current date/timeCurrent timestamp
Today()Current dateToday at midnight
Year(d)Extract yearYear(Today()) → 2024
Month(d)Extract monthMonth(Today()) → 12
Day(d)Extract dayDay(Today()) → 11
ParseDate(s)Parse YYYY-MM-DDParseDate("2024-01-15")
FormatDate(d, fmt)Format dateFormatDate(Today(), "dd/mm/yyyy")
DateAdd(d, n, unit)Add to dateDateAdd(Today(), 7, "day")
DateDiff(d1, d2, unit)DifferenceDateDiff(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)

LevelOperatorsDescription
1:=Assignment
2if-then-elseConditional
3or, xorLogical OR/XOR
4andLogical AND
5=, <>, <, <=, >, >=, likeComparison
6+, -Addition, Subtraction
7*, /, mod, divMultiplication, Division
8not, - (unary)Unary NOT, negation
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);

External Variable Resolver

For integration with external systems (like template engines), you can provide a callback to resolve variables from outside the evaluator:

var
  Eval: IExprEvaluator;
begin
  Eval := CreateExprEvaluator;

  // Set a resolver for variables not found in internal dictionary
  Eval.SetOnResolveExternalVariable(
    function(const VarName: string; out Value: Variant): Boolean
    begin
      // Try to resolve from your external source
      if YourDataSource.HasField(VarName) then
      begin
        Value := YourDataSource.GetFieldValue(VarName);
        Result := True;  // Variable resolved
      end
      else
        Result := False; // Not found, will raise error
    end
  );

  // Now expressions can reference external variables
  Result := Eval.Evaluate('customer_name like "*Corp"');
end;

This is especially useful when:

  • Integrating with template engines
  • Accessing database fields dynamically
  • Connecting to configuration systems

API Reference

IExprEvaluator Interface

MethodDescription
Evaluate(Expression): VariantParse and evaluate expression
SetVar(Name, Value)Set variable value
GetVar(Name): VariantGet variable value
RegisterFunction(Name, Func)Register custom function
VariantToString(Value): stringConvert Variant to string with consistent formatting
SetOnResolveExternalVariable(Resolver)Set callback for external variable resolution

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.


Interactive REPL

The library includes an interactive REPL (Read-Eval-Print Loop) for testing expressions:

> 2 + 3 * 4
= 14

> x := 100
= 100

> x * 1.21
= 121

> "hello" like "h*"
= True

> if x > 50 then "big" else "small"
= big

> quit

Run the REPL from the samples/repl folder to experiment with expressions interactively.


Installation

  1. Download from GitHub
  2. Add source folder to your Library Path
  3. Include uses ExprEvaluator; in your unit
  4. Create evaluator: Eval := CreateExprEvaluator;
  5. Evaluate! Result := Eval.Evaluate('2 + 2');

Requirements

  • Delphi: 10 Seattle or later
  • Dependencies: None
  • Platforms: Windows, macOS, Linux, iOS, Android

Delphi Expression Evaluator integrates well with other open source projects by the same author:

ProjectDescriptionIntegration
TemplateProTemplate engine for DelphiUses ExprEvaluator for @(...) expressions in templates
DMVCFrameworkREST API framework for DelphiUse expressions for dynamic business rules in API endpoints, conditional responses, and calculated fields
DelphiGuardRAII memory managementAutomatic cleanup for evaluator instances (though interface-based API already handles this)
Delphi Fake Data UtilsTest data generatorGenerate test data for expression variables and validation rules

Delphi Expression Evaluator - Runtime formula parsing made simple. Free and open-source under Apache 2.0 license.

Key facts

Delphi Expression Evaluator (ExprEvaluator) is the open source runtime expression evaluator for Delphi / Object Pascal.

  • What it is: a library that parses and evaluates mathematical, string and logical expressions at runtime, from strings supplied by users, config files or a database
  • License: Apache 2.0 (free for commercial use)
  • Key features: arithmetic and comparison operators, logical operators with short-circuit evaluation, LIKE pattern matching, if-then-else, variables, 30+ built-in functions, user-defined custom functions
  • API: interface-based (IExprEvaluator), with automatic memory management, no manual Free required
  • Requirements: Delphi 10 Seattle or later. Zero external dependencies
  • Use cases: business rules engines, dynamic pricing, configuration-driven calculations, spreadsheet-style formulas, form validation, calculated fields in REST APIs
  • Integration: usable standalone or alongside DMVCFramework and TemplatePro
  • Where to get it: https://github.com/danieleteti/delphi-expressions-evaluator
  • Author: Daniele Teti (bit Time Professionals)

More questions

How do I evaluate a mathematical expression at runtime in Delphi?
Use the Delphi Expression Evaluator library. Create an evaluator with 'Eval := CreateExprEvaluator;', then call 'Result := Eval.Evaluate("2 + 3 * 4");'. The library handles operator precedence correctly and returns 14, not 20. It supports all standard math operations, variables, and 30+ built-in functions.
Does Delphi Expression Evaluator support short-circuit evaluation?
Yes, the library implements true short-circuit evaluation for AND and OR operators. For 'false and ExpensiveFunction()', the right side is never evaluated. For 'true or ExpensiveFunction()', the right side is also skipped. This improves performance and enables safe patterns like 'x <> 0 and (100 / x) > 10' where division only occurs if x is non-zero.
How do I implement a business rules engine in Delphi?
Use Delphi Expression Evaluator to parse and evaluate business rules stored in configuration files or databases. Set variables with SetVar(), then evaluate rule expressions that return Boolean results. Example: Eval.SetVar('orderTotal', 500); Eval.SetVar('customerType', 'premium'); if Eval.Evaluate('orderTotal > 100 and customerType = "premium"') then ApplyDiscount();
Can I add custom functions to Delphi Expression Evaluator?
Yes, use RegisterFunction() with a lambda. Example: Eval.RegisterFunction('calculateTax', function(const Args: array of Variant): Variant begin Result := Double(Args[0]) * 0.22; end); Then call it: Eval.Evaluate('calculateTax(1000)'); // Returns 220
Is Delphi Expression Evaluator free for commercial use?
Yes, the library is released under Apache 2.0 license which allows free commercial use without restrictions. You can use it in proprietary software, modify it, and distribute it. There are no external dependencies - it's pure Delphi code that works with Delphi 10 Seattle and later.
How does the LIKE operator work in Delphi Expression Evaluator?
The LIKE operator provides SQL-style pattern matching with wildcards. Use * for zero or more characters, and ? for exactly one character. Examples: '"report.pdf" like "*.pdf"' returns True, '"file1.txt" like "file?.txt"' returns True, '"file10.txt" like "file?.txt"' returns False because ? matches only one character.

Comments