Version: 1.6.3
Status: Stable
Released: 2026-10-08
License: MIT License
No downloadable files are available for this release but you can obtain the source code from GitHub.
Source-Code

This is open source software licensed as MIT License. You can obtain the source code from GitHub or browse the releases for source code associated with specific versions. If you make any changes which you feel improves this application, please feel free to submit a pull - request.

Release Notes

Be sure to check out the NuGet package https://www.nuget.org/packages/NTDLS.ExpressionParser/ 📦

Highlights

The engine has been rewritten. Expressions are now parsed, validated and compiled in a single pass to a compact program that evaluates without allocating, and many long-standing parsing bugs are fixed along the way.

  • ~44 ns to evaluate a 5 operation expression with variables (≈ 23M evaluations/s) - about 8× faster than NCalc.
  • ~0.5 μs to parse, compile and evaluate a brand-new expression - the fastest of the 9 .NET expression parsers benchmarked.
  • Expressions without variables are computed once at compile time: ~379 ns → ~5 ns per evaluation.
  • Benchmarked against NCalc, Flee, Jace, MathEvaluator, xFunc, NoStringEvaluating, CalcExpr.NET and Mathos Parser - see the README.

Performance

  • Expressions compile to a postfix program evaluated on the stack, with constant folding.
  • Variables are resolved by SetParameter, so Evaluate() never looks up a name.
  • Static Expression.Evaluate(text): ~1,737 ns / 5.6 KB → ~150 ns / 344 B (compiled expressions are cached).
  • Parsing is a single pass with no regular expressions, and allocates about half as much as before.
  • No code generation, so new expressions have no JIT compilation cost.

New

  • Expression.OperationCount - the number of operations an expression performs.
  • == operator (same as =).
  • ~ now works, as unary bitwise NOT.
  • Show work (Evaluate(out showWork)) lists every operation in the order performed, with variable values substituted.
  • No limit on nesting depth - 100,000 nested parentheses or 20,000 nested function calls no longer overflow the stack.

Bug fixes

  • Variables were replaced as plain text, corrupting function names and other variables (abs(a), max(max, 1)).
  • A function whose name starts another's could be called instead (e() vs exp(), f() vs foo()).
  • Functions without parameters crashed (pi()).
  • Negated groups and variables failed (-(2+3), -x + 1).
  • A signed operand after a comparison returned null (2 > -1).
  • Comparison operators did not take precedence over logical operators (1 = 2 || 3 = 3 returned 0).
  • Decimal literals were parsed imprecisely (0.3 returned 0.30000000000000004).
  • Expressions with different DefaultNullValue options could share a cache entry and return the wrong null value.
  • A negated variable inside parentheses could return a stale result after the variable changed ((-x) * 2).
  • Operands separated only by whitespace were silently joined (a b became the variable ab, 2 3 became 23).
  • Consecutive signs gave wrong results (1 + --2 returned -1).
  • Malformed expressions threw unhelpful exceptions such as IndexOutOfRangeException.
  • A literal $ in an expression could act as an internal placeholder.

Behavior changes

Please review these when upgrading - some can change results:

  • Operator precedence now follows C: shift, then comparison, then equality, then &, ^, |, &&, ||. Previously all of these had equal precedence and were evaluated left to right.
  • Syntax errors are thrown by the constructor (new Expression(...)) rather than by Evaluate(), as Syntax error: ... at position N of '...'. Positions are character indexes in the expression as written.
  • Show work output has a new format: one line per operation, e.g. 5+1000 = 1005.
  • Rejected input:
    • operands separated only by whitespace (a b, 2 3)
    • operators containing whitespace (a < = b)
    • empty function parameters (max(1,))
    • empty parentheses (())
    • the $ character
  • Up to 3 consecutive operator characters are now allowed (was 2), so 2 >= -1 parses.

Documentation

  • The README now covers operators and precedence, every built-in function, null handling, options, errors, performance with benchmarks, and thread safety.
  • The XML docs for ExpressionOptions.Precision (it only formats show-work output) and CustomHash are corrected.

Testing

  • Changes were verified against differential fuzzing (reference evaluator and engine-to-engine), a fixed 74,552-result corpus, a 200,000-input malformed-expression corpus, and multi-threaded stress tests.