Module Composition
Module composition is the mechanism that turns selected language features into a runnable DSL.
A module is not just a label in a dialect file. A real module may contribute lexer rules, parser nodes, AST visitors, bytecode/AIR lowering, optimizer support or backend behavior. A dialect selects the modules that are allowed to participate in a specific runtime host.
When to read this page
Read this page after Dialect Files and Minimal DSL when you want to understand why adding or removing one module changes what the language can parse and execute.
Goal
Understand how modules combine into a language surface and which mistakes break modularity.
Composition model
A simplified composition flow looks like this:
.wistdialect
-> dialect compiler
-> selected module aliases
-> manifest/runtime resolution
-> deterministic module order
-> frontend/core/backend services
-> execution hostThe dialect file is the user-facing selection layer. Runtime manifests and DI registration resolve the selected aliases to implementation types. The final host should be deterministic: the same dialect and repository state should produce the same runtime composition.
What a module can own
A module may own one or more of these responsibilities:
| Responsibility | Example |
|---|---|
| Lexemes | numeric literals, operators, keywords |
| Parser behavior | expression nodes, loop syntax, condition syntax |
| AST nodes | typed representation of a syntax construct |
| AST translation | conversion from AST to bytecode/AIR |
| Bytecode/AIR shape | semantic tags, stack operations, intermediate instructions |
| Optimizer support | arithmetic folding, native lowering, backend-specific transformations |
| Backend behavior | interpreter execution or CIL emission for the owned semantics |
The boundary matters. If arithmetic owns arithmetic operators, another module should not parse arithmetic syntax by scanning raw source text. If variables own variable binding, backend code should not silently reinterpret unrelated identifiers as runtime arguments.
Minimal example
This dialect:
dialect MinimalArithmetic
use Arithmetic,Numbers,Scopes,Whitespaces
backend interpreteris a composition of four capabilities:
Numbersprovides numeric values.Arithmeticprovides arithmetic operations.Scopesprovides expression/block structure needed by the parser/runtime path.Whitespaceskeeps lexical handling usable for normal source text.
It can run:
2 + 3 * 4but it should not accept:
let x = 2
x + 1unless Identifier and Variables are also selected.
Dependency discipline
Some modules are meaningful only with related modules. For example:
Variablesusually requiresIdentifier.- conditional syntax usually requires comparison/equality/boolean support, depending on the exact program shape;
- native/CIL-oriented optimizations require backend support;
- interop modules should not appear in a restricted user-facing formula DSL.
The documentation should not pretend that module composition is magic. A dialect author is responsible for selecting a coherent set of capabilities and testing it.
Deterministic ordering
Composition must be deterministic because parser priority, AST visitors, optimizers and backend services can depend on order.
Good composition behavior:
- aliases resolve to the same implementation types every time;
- parser priorities are explicit;
- module ordering is either declared, derived deterministically or tested;
- tests fail when an unexpected module appears in the runtime surface.
Bad composition behavior:
- relying on reflection enumeration order;
- adding a hardcoded special case for a module instead of fixing composition;
- treating backend IDs or module aliases as magic strings scattered across unrelated files;
- using global mutable registries that change between test runs.
Composition vs. security
A restricted dialect reduces the available language surface. That is useful, but it is not the same thing as process isolation.
Use restricted dialects to say:
This formula language does not include variables, loops or interop modules.Do not use restricted dialects alone to say:
This is safe for arbitrary untrusted third-party code in the same process.For untrusted third-party code, combine dialect restriction with process, environment and resource isolation.
Common mistakes
- Adding a module because a test passes, without documenting what syntax it enables.
- Starting from
full-defaultfor a production formula DSL and forgetting to remove unnecessary features. - Enabling an optimizer before the unoptimized path is correct.
- Depending on one backend while documenting the dialect as backend-neutral.
- Adding marker-only capabilities that imply behavior without implementation ownership.
- Hiding missing module dependencies behind fallback parser behavior.
- Copying syntax from secondary parser experiments instead of the runtime
.wistdialectformat used by shipped profiles.
Practical checklist
Before accepting a dialect composition, verify:
- the dialect file lists only intended modules;
- omitted syntax is rejected;
- selected backends match the intended runtime modes;
- compiler and interpreter results match when both are enabled;
- interop modules are absent from restricted user-authored DSLs unless explicitly intended;
- optimizer support is tested separately from base semantics.
Next
Continue with Backend Selection.