Expreszo.LanguageServer 0.4.3

dotnet add package Expreszo.LanguageServer --version 0.4.3
                    
NuGet\Install-Package Expreszo.LanguageServer -Version 0.4.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Expreszo.LanguageServer" Version="0.4.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Expreszo.LanguageServer" Version="0.4.3" />
                    
Directory.Packages.props
<PackageReference Include="Expreszo.LanguageServer" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Expreszo.LanguageServer --version 0.4.3
                    
#r "nuget: Expreszo.LanguageServer, 0.4.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Expreszo.LanguageServer@0.4.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Expreszo.LanguageServer&version=0.4.3
                    
Install as a Cake Addin
#tool nuget:?package=Expreszo.LanguageServer&version=0.4.3
                    
Install as a Cake Tool

<picture> <source media="(prefers-color-scheme: dark)" srcset="docs/logo_dark.png"> <img src="docs/logo.png" alt="ExpresZo" width="420"> </picture>

ExpresZo .NET

NuGet CI codecov

A safe, extensible expression evaluator for .NET - a configurable alternative to eval(). Parses and evaluates the same expression language as expreszo-typescript.

Read full documentation

Highlights

  • Pratt parser, immutable AST, 27 operators, 82 built-in functions.
  • JsonDocument-based I/O - variables in, results out, using System.Text.Json primitives.
  • Single async-capable evaluator with a synchronous fast path (ValueTask<Value> under the hood).
  • Native AOT and trim compatible - zero reflection, zero runtime code generation. CI verifies on every PR.
  • Thread-safe by design - one Parser instance can serve many threads; parsed Expressions are immutable and safe to share.
  • net10.0 target.

Install

dotnet add package Expreszo

Targets net10.0. Packages are published to NuGet.org automatically on every v*.*.* tag - see the releases page for changelogs.

Quick start

using System.Text.Json;
using Expreszo;
using Expreszo.Json;

var parser = new Parser();

// Simple evaluation.
var result = parser.Evaluate("1 + 2 * 3");
Console.WriteLine(result);                           // 7

// Variables from a JsonDocument.
using var scope = JsonDocument.Parse("""{"x":10,"y":32}""");
Console.WriteLine(parser.Evaluate("x + y", scope));  // 42

// Parse once, evaluate many times.
var expr = parser.Parse("base * qty * (1 - discount)");
using var v = JsonDocument.Parse("""{"base":25,"qty":4,"discount":0.1}""");
Console.WriteLine(expr.Evaluate(v));                 // 90

// Higher-order: lambdas and array callbacks.
using var data = JsonDocument.Parse("""{"items":[10,20,30,40]}""");
Console.WriteLine(parser.Evaluate("sum(filter(items, x => x > 15))", data));  // 90

// JSON in → JSON out. The result is a Value, which JsonBridge serialises
// back to System.Text.Json primitives with no reflection.
using var order = JsonDocument.Parse("""{"price":25,"qty":4,"tax":0.21}""");
var totals = parser.Evaluate(
    "{ subtotal: price * qty, total: price * qty * (1 + tax) }",
    order);
Console.WriteLine(JsonBridge.ToJsonString(totals));
// {"subtotal":100,"total":121}

// Dynamic variable resolver - called per reference, only for names the
// scope didn't already bind. Return NotResolved to fall through.
var env = new Dictionary<string, string>
{
    ["REGION"] = "eu-west-1",
    ["USER"] = "alice",
};
VariableResolver resolveFromEnv = name => env.TryGetValue(name, out var value)
    ? new VariableResolveResult.Bound(new Value.String(value))
    : VariableResolveResult.NotResolved;
Console.WriteLine(parser.Evaluate(
    "USER | \" @ \" | REGION",
    resolver: resolveFromEnv));
// alice @ eu-west-1

Expression language - cheat sheet

Category Examples
Literals 42, 3.14, 1e10, 0xFF, 0b1010, "hello", true, false, null, undefined
Arithmetic +, -, *, /, %, ^ (power), \| (concat)
Comparison ==, !=, <, <=, >, >=, in, not in
Logical and / &&, or / \|\|, not / !
Ternary / coalesce cond ? a : b, x ?? fallback
Type cast x as "number", x as "int", x as "boolean"
Member access obj.name, xs[0]
Arrays & spread [1, 2, ...rest]
Objects & spread { a: 1, "b-key": 2, ...base }
Arrow functions x => x * 2, (a, b) => a + b
Function defs f(x, y) = x + y; f(1, 2)
Assignment x = 5; x + 1
Case case x when 1 then "one" when 2 then "two" else "other" end
Sequences a = 1; b = 2; a + b

Built-in functions

Math (17): atan2, clamp, fac, gamma, hypot, max, min, pow, random, roundTo, sum, mean, median, mostFrequent, variance, stddev, percentile

Array (20): count, filter, fold, reduce, find, some, every, unique, distinct, indexOf, join, map, range, chunk, union, intersect, groupBy, countBy, sort, flatten

String (28): length, isEmpty, contains, startsWith, endsWith, searchCount, trim, toUpper, toLower, toTitle, split, repeat, reverse, left, right, replace, replaceFirst, naturalSort, toNumber, toBoolean, padLeft, padRight, padBoth, slice, urlEncode, base64Encode, base64Decode, coalesce

Object (7): merge, keys, values, mapValues, pick, omit, flattenObject

Utility (2): if (lazy), json

Type-check (8): isArray, isObject, isNumber, isString, isBoolean, isNull, isUndefined, isFunction

Design notes

Three behaviours to know about:

  • Undefined is distinct from Null. Missing members (obj.nope) and lambda parameters you didn't pass return Value.Undefined; explicit JSON nulls return Value.Null. Reading a completely unknown identifier is a hard error - it throws VariableException rather than returning Undefined, so typos don't silently evaluate to nothing. The ?? operator, isUndefined / isNull, and short-circuit semantics all depend on the Null/Undefined distinction.
  • Assignments don't propagate back to your input JsonDocument. The evaluator copies the document into an internal scope on entry. Assignments mutate that scope only; the caller's document is untouched. (JsonDocument is immutable in System.Text.Json.)
  • Numbers are IEEE 754 double - same semantics as JavaScript. Very large integers in your JSON lose precision; serialise them as strings if you need exact round-tripping. There is no decimal mode.

Security

The library validates every access point before evaluating it:

  • Properties named __proto__, prototype, or constructor are always rejected - both via dot access and via bracket-string access. The same names are filtered out of JSON I/O and scope bindings for defence-in-depth.
  • Array indices must be finite non-negative integers.
  • Only registered functions (built-ins + any you add via a future OperatorTableBuilder API) can be invoked. Raw CLR delegates smuggled in through a custom VariableResolver or Scope binding are rejected at the call site via an identity-based allow-list.
  • Recursion is capped at 256 levels of nested calls and 256 levels of parse depth - runaway recursion (f(x) = f(x); f(1)) throws an EvaluationException instead of a StackOverflowException.
  • Resource-heavy built-ins (repeat, padLeft/Right/Both, range, fac, postfix !) enforce output-size and input-range budgets published as Expreszo.EvaluationLimits constants.
  • EvaluateAsync's CancellationToken is observed on every call boundary and inside every looping built-in, so timeouts are honoured in bounded time.

AOT-safe

The library assembly is built with IsAotCompatible=true and every trim / AOT analyser enabled. The CI matrix includes a dedicated job that runs:

dotnet publish samples/AotCheck --configuration Release -r linux-x64 --self-contained -p:PublishAot=true

and executes the resulting native binary. Any code path introducing reflection or dynamic code generation fails the build.

Your app can enable <PublishAot>true</PublishAot> without any warnings from this library.

Benchmarks

BenchmarkDotNet-based micro-benchmarks live in bench/Expreszo.Benchmarks/ and cover parsing, evaluation, simplification, and end-to-end parse+evaluate cycles:

dotnet run --project bench/Expreszo.Benchmarks -c Release -- --list flat        # list available benchmarks
dotnet run --project bench/Expreszo.Benchmarks -c Release                       # run the full matrix (~minutes)
dotnet run --project bench/Expreszo.Benchmarks -c Release -- --filter '*Eval*'  # run a subset

Tooling

A Language Server Protocol (LSP) implementation lives in src/Expreszo.LanguageServer and src/Expreszo.LanguageServer.Host. The host is a stdio server consumable from any LSP-aware editor (VS Code, Neovim, Zed, JetBrains, Emacs). Current coverage: diagnostics (syntax + statically-detectable type issues), hover, completion, signature help, document symbols, goto-definition, find-references, rename, and semantic tokens. A VS Code extension and published host binaries are the next things on the roadmap.

Error-recovering parsing and the type-tag enum are exposed on the library API — Parser.TryParse(string) returns a ParseResult with a best-effort expression plus an ImmutableArray<ExpressionException>, and Expreszo.ValueKind is the coarse type tag used by the language server's literal-driven validator. Library callers that want the same diagnostics flow are free to wire those pieces together without the server.

Out of scope

  • MCP server.
  • Legacy-mode semantics.
  • Runtime disabling of specific operators.
  • Customising the built-in operator set (adding custom named operators). Adding custom functions will be supported via OperatorTableBuilder in a future release.

License

MIT.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.3 79 6/2/2026
0.4.1 101 4/24/2026
0.4.0 96 4/24/2026