An engine kit for building small, AI-native DSLs in Rust.
Write your AST as a Rust enum and derive the rest: from a few derives, the kit supplies a schema, a text parser, a JSON front-end, a typed builder, lints, a debugger-grade interpreter that executes the enum directly, and an MCP surface an AI can drive. The Rust type is the single source of truth — everything user-facing (grammar, examples, diagnostics, tool schemas, execution) is generated from it, so none of it can drift.
use dsl_kit::{IdGen, NodeId};
use dsl_kit_macros::{DslBuild, DslExec, DslNode, DslSchema};
#[derive(Debug, DslNode, DslSchema, DslBuild, DslExec)]
enum Expr {
#[dsl_exec(value)]
Lit { id: NodeId, value: i64 },
#[dsl_exec(apply = "add")]
Add { id: NodeId, lhs: Box<Expr>, rhs: Box<Expr> },
#[dsl_exec(branch)]
If { id: NodeId, cond: Box<Expr>, then_branch: Box<Expr>, else_branch: Box<Expr> },
}That single declaration already gives you:
use dsl_kit_parse::{DslBuild as _, schema_gen};
use dsl_kit_schema::DslSchema as _;
// A grammar for a canonical text syntax, generated from the schema —
// left-recursion-free by construction and verified by static checks.
let grammar = schema_gen::checked_grammar_from_schema(&Expr::schema(), &IdGen::new())?;
// Parse canonical text, then build the typed AST back out of it.
let tree = grammar.parse("Add(lhs: Lit(value: 40), rhs: Lit(value: 2))")?;
let expr = Expr::from_parse_tree(&tree, &IdGen::new())?;
// Machine-derived examples that parse by construction — few-shot
// material for an AI writing your DSL.
let examples = dsl_kit_parse::example_gen::examples_from_grammar(&grammar)?;A JSON front-end ({"type": "Add", "lhs": ...}) targets the same
ParseTree, so both surfaces share one conformance check and one typed
builder. Payload types the canonical syntax cannot spell get per-field
hooks on both sides: schema_gen::SyntaxOverrides for the grammar,
#[dsl_build(with = ...)] for the builder.
dsl-kit-core interprets effect-oriented, value-bearing programs —
Seq / Par / Call / Scope / Maybe for orchestration, Apply /
Branch / Bind / Read / Loop / Lit for computation — with the
runtime mechanics a real host needs:
- Structured fan-out:
Parwith join policies (All/Any/FirstK, fail-fast or collect-all) folded by pluggable reducers. - Suspend / resume: every
Callyields a suspension the host resolves withOk(value)orErr(effect_error); cancellation is cooperative and observable. - Debugger-grade breakpoints: every spawn passes through one
schedule, so a breakpoint halts before the matched frame spawns —
at any depth, including mid-
Par-cascade — and the frame tree faithfully exposes the partially spawned state. - Sans-io drive layer:
drive(sync) anddrive_async(any executor; the core has zero runtime dependencies) run the resolve loop for you when you don't want to hand-roll it. - Value semantics, interpreted:
Applyfolds evaluated children through a registered op,Branchselects on a value (the untaken side never spawns),Bind/Readcarry a lexical env chain,Looprespawns its body per iteration — all inside the same frame machinery, so breakpoints halt on branch selection and on every loop iteration, and an unboundReadsuspends like any other effect. You register what"add"means; the engine does the rest. No hand-written eval loop —expr-exampleruns entirely on the engine.
dsl-kit-mcp wraps any DslHost in a stdio MCP server with a
debugger-style tool surface: load, ast, step (one / to-yield /
to-done), breakpoint add/list/remove, state, pending, resolve,
schema, lint, explain. The custom-mcp-example shows how to ship
your own DSL as its own MCP server binary.
| crate | role |
|---|---|
dsl-kit |
facade — re-exports the kit surface |
dsl-kit-core |
engine: frames, fan-out, cancellation, events, breakpoints, drive |
dsl-kit-macros |
#[derive(DslNode)] / #[derive(DslSchema)] / #[derive(DslBuild)] / #[derive(DslExec)] |
dsl-kit-schema |
type-level schema consumed by parsers, editors, AI clients |
dsl-kit-parse |
ParseTree, conformance, JSON bridge, PEG interpreter, grammar generation, example synthesis |
dsl-kit-lint |
walk-driven, schema-aware, author-extensible lints |
dsl-kit-mcp |
stdio MCP framework over any DslHost |
cargo run -p expr-example # expression DSL: text + JSON round trips, schema-generated grammar
cargo run -p flow-example # orchestration DSL: fan-out, value-gated Branch, breakpoints, drive layer, text round trip
cargo run -p cfg-example # configuration DSL: keyed child slots (BTreeMap), override folds, host-resolved referencesThe examples double as reference implementations: flow-dsl shows the
per-field hooks on a DSL with exotic payload types, expr-dsl shows
the plain derive-only path end to end, and cfg-dsl shows keyed child
slots — named children rather than positional ones — through both
front-ends and over MCP (see
examples/cfg-example).
API documentation is the source of truth and is written to be read:
cargo doc --openThe kit is young and the API is still moving. The engine semantics (fan-out, cancellation, halt-before-spawn breakpoints) and the derive contracts are test-pinned; surface ergonomics may change between minor versions. See CHANGELOG.md for the release history.
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.