Skip to content

Embedding in .NET

UniversalToolchain is intended to be embedded into .NET applications. The CLI is useful for learning and smoke checks, but production-style DSL usage normally runs through a host API.

When to read this page

Read this after Minimal DSL when you want to run formulas or restricted scripts from application code.

Goal

Create a Wist runtime facade, select a shipped dialect preset, pass source text and named inputs, choose a backend and read the result.

Minimal host shape

A host application should treat the DSL runtime as a composed execution surface:

text
select dialect or preset
  -> create runtime facade
  -> pass source and declared inputs
  -> choose requested backend
  -> receive result or explicit failure

The facade is an entry point into the same dialect/runtime composition model. It is not a second implementation of Wist. This page uses lower-level dialect-runtime APIs; first-contact application code should prefer the UniversalToolchain.Wist package facade unless it specifically needs custom dialect composition.

Example: restricted pricing expression

csharp
using UniversalToolchain.Dialects.Wist.Facade;

using var runtime = WistRuntimeFacadeBuilder
    .CreateDefault()
    .WithShippedDialectPreset("pricing-restricted")
    .Build();

var result = runtime.Run(
    "price + fee * 2",
    new Dictionary<string, object?>
    {
        ["price"] = 100,
        ["fee"] = 5
    },
    backend: "compiler");

Expected numeric result:

text
110

The important contract is that the host selects a dialect, supplies inputs explicitly and requests a backend intentionally.

Why inputs are explicit

Runtime inputs such as price and fee are host-provided bindings. They are not the same concept as local variables declared by the Wist program.

A correct runtime path must keep these concerns separate:

text
external binding: provided by the host
local variable: declared and mutated by the program

This boundary matters for formula DSLs because a host parameter should not accidentally overwrite an unrelated local variable.

Choosing a dialect for embedding

Use the narrowest dialect that supports the business case.

ScenarioRecommended dialect shape
Pricing/scoring formulaarithmetic, identifiers, selected variable/input support, no interop
Workflow-like scriptvariables, conditions and loops only if needed
Demonstrating Wistbroad reference profile
Untrusted user-authored codenarrow dialect plus process/resource isolation outside Wist

Do not embed full-default only because it is convenient. A broad profile exposes more syntax and runtime behavior than a restricted formula surface usually needs.

Backend selection

backend: "compiler" requests the CIL-backed compiler path when the selected dialect exposes CIL. backend: "interpreter" requests the interpreter path when exposed.

When both backends are available, add parity tests for important formulas. When only one backend is available, test that the unsupported backend request fails explicitly.

What to test in a host application

A host using Wist should test:

  • valid formula execution;
  • missing input behavior;
  • wrong input type behavior;
  • syntax rejected when the owning module is omitted;
  • backend availability failures;
  • compiler/interpreter parity when both modes are exposed;
  • interop rejection for restricted formula dialects.

Security boundary

A restricted dialect is a composition constraint. It limits which modules, syntax and runtime components are selected. It is not a hardened process sandbox.

For arbitrary untrusted third-party code, combine dialect restriction with external isolation, resource limits and host-level policy.

Next

Continue with Backend Selection, Testing a DSL and Backend Contracts.

Built for developers who want to use, extend, or understand UniversalToolchain.