Become a member!

Delphi Expression Evaluator - Runtime Formula Parser & Business Rules Engine

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

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

  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:

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

  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:

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

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

Comments

comments powered by Disqus