DMS Dialects
This directory holds dialect specs for DMS tier 1. Each dialect is a published contract — families, sigil bindings, param signatures, content-slot rules, version-match strategy — that any tier-1-capable port can register and validate against.
The tier-1 framework itself (decorator grammar, sidecar shape, hoisting, decoder/encoder split, mutate-API contract, multi-line flow layout) is defined in TIER1.md. Dialects plug into that framework; they don't redefine it.
Published dialects
| Dialect | Brand | File ext | Spec | Status |
|---|---|---|---|---|
| HTML | dms+html |
.dms.html |
dms+html.md | 0.1 (draft) |
| HCL/Terraform | dms+hcl |
.dms.hcl |
dms+hcl.md | 0.1 (draft) |
| KDL v2 | dms+kdl |
.dms.kdl |
dms+kdl.md | 0.1 (draft) |
| RON | dms+ron |
.dms.ron |
dms+ron.md | 0.1 (draft) |
| Kubernetes | dms+k8s |
.dms.k8s |
dms+k8s.md | 0.1 (draft) |
What kinds of formats fit a DMS dialect
The dialect framework targets data with metadata about that data. It does not target programs, constraint languages, or expression-heavy formats. The split matters because the framework's value comes from a clean separation between the value tree (data) and the decoration sidecar (metadata) — when source is mostly computation or constraints, there's no natural "data half" to put in the value-column, and the dialect ends up pushing everything into the sidecar as opaque strings.
The rule, stated cleanly:
A format fits when most of the source is data and the decoration is metadata about that data. It doesn't fit when most of the source is computation, constraints, or references.
Formats checked so far
| Format | Verdict | Why |
|---|---|---|
| HTML / SVG | ✓ fits | Element-shaped data; tag + attrs are metadata about the element |
| HCL / Terraform | ✓ fits | Block-shaped data; expressions confined to @expr opaque strings |
| KDL v2 | ✓ fits | Node-shaped data with positional + named + children |
| RON | ✓ fits | Tagged ADT data; tags ARE the metadata, payload is data |
| ZON | ✓ fits | Anonymous structs/lists with tagged literals; mostly tier-0 plus tags |
| JSON Schema | ✓ fits | Schema-shaped — types and constraints structured, no general-purpose computation |
| Jsonnet | ✗ rejects | Programming language; lambdas, conditionals, imports dominate |
| Dhall | ✗ rejects | Typed lambda calculus; computation-first by design |
| CUE | ✗ rejects | Constraint language; unification, references, computation everywhere |
| KCL | ✗ rejects | Constraint language with schemas; same shape as CUE structurally |
What "data with metadata" looks like in practice
The fits all share a structural property: a reader scanning for
values lands on a stable column. The metadata sits in
decoration calls (|tag(...), @type ...) that the reader can
either focus on or skip. Compare:
+ |resource("aws_instance", "web")( ← metadata: kind + labels + attrs
ami: "ami-...", ← data: in the value-column
instance_type: "t2.micro",
)
+ |player(name: "Alice", score: |some 42) ← metadata: variant + struct
In both cases, the data values ("ami-...", "t2.micro",
"Alice", 42) occupy a predictable column on every line.
Tools that strip the decoration in lite mode get back a clean
value tree.
What rejected formats look like
The rejected formats don't have a clean "values" column to begin with — every position is potentially an expression that needs evaluation. A faithful dialect representation would put a decoration on every value:
# Hypothetical dms+jsonnet — most lines are decorations
+ |local(name: "x", value: 5)
+ |local(name: "y", value: 10)
+ |body
+ sum: |add(|ref "x", |ref "y") ← every value is a decorator call
The value-tree is mostly empty defaults; the data lives entirely
in the decoration sidecar's params. Lite mode produces an
unhelpful "all empty" tree. Generic tooling can't operate on the
value tree because there's no usable data there. The dialect
delivers no benefit over storing the original source as a string
in a single DMS field.
Before proposing a new dialect
Ask: "If I strip every decoration from the source, is what's left valid, useful data, or is it gibberish empty maps and lists?"
- If valid useful data → likely fits. Proceed with a draft spec following the structure in Adding a new dialect.
- If gibberish → likely doesn't fit. Consider one of these alternatives:
- Use a templating language on top of DMS. This is the recommended path for computation-shaped sources. See Templating on DMS below.
- Carve out a structured subset (e.g., dms+json-schema out of CUE's territory, dms+macros for the deduplication-only subset of jsonnet).
- Build a separate tool, not a dialect. "Source as string" plus a tool that processes it doesn't need to live inside DMS.
Templating on DMS
DMS is a data format. It deliberately doesn't include expressions, variables, conditionals, imports, or computation — that's the Goal and applies at every tier. For cases where you genuinely need computation to produce a config, run a templating language on top of DMS. The templating layer handles the computation; DMS is the output format that consumers actually load.
This is a clean separation of concerns:
template source (jsonnet / Jinja / Helm / KCL / CUE / your tool)
↓ evaluation
DMS document (.dms / .dms.html / .dms.hcl)
↓ tier-0 or tier-1 decode
Document (Map / List / Scalar [+ decoration sidecar])
The templating layer can be:
- Any general-purpose templating engine — Jinja, Mustache,
ERB, EJS, Handlebars, Liquid, Go's
text/template. These produce text; configure them to produce DMS-shaped text. - A typed config language used as a template engine — jsonnet, CUE, KCL, Dhall, Nickel can all emit DMS instead of (or in addition to) JSON. They get the typing/computation benefits; DMS gets the data with comments and round-trip preservation.
- A purpose-built tool that takes parameters and emits DMS.
Why this separation works:
- Computation stays where it belongs — in a language designed for computation, with type-checking, error messages, IDE support, and a real evaluator.
- Data stays where it belongs — in DMS, with comments, round-trip preservation, lite-mode access, generic tooling.
- Consumers don't need a templating runtime — they decode DMS, full stop. No template engine in the consumer's dependency tree.
- Round-trip works — once the templating produces a DMS file, that file is plain DMS. Edit it, re-encode it, diff it, validate it with any DMS-aware tool. No re-evaluation needed.
Examples of this pattern in the wild (analogous):
- Helm templates generate Kubernetes YAML. Operators read the generated YAML, not the templates.
- Jsonnet generates JSON. Consumers read JSON.
envsubstgenerates configs from environment variables. Daemons read the substituted output.
A dms+jsonnet or dms+cue dialect would invert this — try to
move the templating layer itself into DMS — and that's where
the model breaks. Keep templating outside; let DMS be the
output target.
If you build such a tool, the dms-<lang> ports (Rust, Python,
Lua, etc.) provide encoder APIs that accept Map / List /
Scalar value trees plus an optional decoration sidecar and
emit canonical DMS. That's typically all the templating layer
needs to bind against.
Conventions
- Brand identifier.
dms+<brand>form (the+mirrors EBNF / MIME dialect notation; avoids collision with port-namingdms-<lang>). - File extension.
.dms.<brand_extension>so editors and grep can dispatch on the secondary extension. The extension is a tooling hint, not a binding declaration — every dms+X document must still set_dms_tier: 1and declare its_dms_imports. - Versioning. Every dialect ships its semver version in the
canonical spec. File-side imports declare a target version;
the dialect's
version_strategy(defaultcaret) controls matching. - Pre-1.0 status. All current dialects are 0.x. Breaking changes are still possible. No version-bump rules apply yet.
Adding a new dialect
A new dialect spec is a markdown file alongside the existing
ones. The structure that has settled across dms+html and
dms+hcl:
- Header — brand, extension, version, parent spec links
- What this is — one paragraph framing
- Quick comparison — same content in source format vs dms+X form, side by side
- Dialect canonical spec — the formal contract written in DMS itself (families, sigils, content_slot, params)
- Content semantics — how source forms map to value tree + sidecar
- Inventory — representative names the dialect handles
- Versioning — what counts as breaking / additive / patch
- File extension
- What's not in scope — explicit non-goals
- Open questions for v0.2+
Dialects don't need a bench / conformance corpus per se —
tier-1 conformance is matrixed (port × dialect → pass count)
in TIER1.md §"Conformance", and corpus fixtures live in
dms-tests under per-dialect subdirectories (planned).
Governance (parked)
Allocation of short brand names (html, hcl, markdown, etc.)
is currently first-come-first-served via PRs to this directory.
A formal registry is a parked open question; see TIER1.md
§"Branding & file naming" → "Open: dialect registry governance."
For unofficial / experimental work: prefix with x-
(x-mybrand) or use reverse-DNS namespacing
(io.flolabs.html).