NTDLS.ExpressionParser v1.6.3
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
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, soEvaluate()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()vsexp(),f()vsfoo()). - 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 = 3returned0). - Decimal literals were parsed imprecisely (
0.3returned0.30000000000000004). - Expressions with different
DefaultNullValueoptions 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 bbecame the variableab,2 3became23). - Consecutive signs gave wrong results (
1 + --2returned-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 byEvaluate(), asSyntax 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
- operands separated only by whitespace (
- Up to 3 consecutive operator characters are now allowed (was 2), so
2 >= -1parses.
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) andCustomHashare 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.
This is open source software licensed as MIT License. You can obtain the source code from