A config file format for humans.

DMS — Data Markup Syntax — aims to be a config language with YAML's clean look, TOML's small strict spec, and one extra superpower: comments survive parse → modify → re‑emit, in every reference parser, in every language.

+++
title:     "DMS feature tour"
version:   "1.0.0"
updated:   2026-04-24T09:30:00-04:00
+++

# Line comments take # or //, your pick.
// Bare keys allow full Unicode. Heredocs are first‑class.

database:
  host:     "db.internal"
  port:     5432                  # raised after the LB change
  pool:     { size: 10, idle_timeout_s: 30 }

servers:
  + name: "web1"
    disks:
      + mount: "/"
        size_gb: 100
      + mount: "/var"
        size_gb: 500
  + name: "web2"

sql: """SQL _trim("\n", ">")
    SELECT id, email
      FROM users
     WHERE active = true
    SQL

regions: ["us-east-1", "eu-west-1", "ap-south-1"]

Indent-based, one rule

Siblings under a parent must share one indent width. Tabs are banned in structural indent. That is the entire indent specification.

Distinct types, never inferred

Quoted is a string. Bare digits are a number. true is a boolean. There is no NO-becomes-false, no octal-from-leading-zero, no Norway problem.

Comments are first-class

Leading, trailing, and floating comments are AST nodes. They survive parse, mutation, and emit — by spec, in every reference parser.

Polymorphic root

A document can be a table, a list, a scalar, or empty. Want to serialize a list? Just write a list — no wrapper key, no ceremony.

Heredocs with modifiers

Labelled heredocs in basic and literal flavors, with chainable modifiers like _trim and _fold_paragraphs. Every YAML block-scalar mode is one combo away.

Seven first-party parsers

Rust, C, Go, Zig, Python, Perl, JavaScript. All hold at 4665 / 4665 on the shared conformance corpus.

Why a new config format?

Same data, three formats. The differences aren't cosmetic.

YAML
servers:
  - name: web1
    disks:
      - mount: /
      - mount: /var
  - name: web2

countries:
  - US
  - UK
  - NO          # silently false
version: 1.0     # float, not "1.0"
port:    011     # octal 9 in YAML 1.1

Clean shape. Eighty-five-page spec, plain scalars that type-coerce on what they look like, anchors and merge keys baked into the data model.

TOML
[[servers]]
name = "web1"

[[servers.disks]]
mount = "/"

[[servers.disks]]
mount = "/var"

[[servers]]
name = "web2"

Tight grammar, unambiguous types. Then you nest something and the path repeats five times. No block comments, no list-or-scalar root, no heredocs.

DMS
servers:
  + name: "web1"
    disks:
      + mount: "/"
      + mount: "/var"
  + name: "web2"

countries:  ["US", "UK", "NO"]
version:    "1.0"            # quoted = string
port:       11               # bare digits = decimal
octal_perms: 0o644            # explicit prefix

YAML's shape. TOML's strictness. + for list items so paths never repeat. One indent rule, distinct types, a small spec you can read over lunch.

A quick tour

Every feature DMS has, in one short scroll.

Comments — line and block

# A line comment.
// Or this style. Both are equivalent.

port: 8080   # trailing comments work too

###NOTE
  Block comments. Disable any chunk of DMS — including this
  paragraph — without escaping line by line. The labelled fence
  ###LABEL … LABEL rhymes with the heredoc syntax below.
  NOTE

Strings — basic, literal, heredoc

basic:    "escapes are processed: \n \t é"
literal:  'C:\Users\ada — backslashes stay backslashes'

# Strip trailing newlines (YAML's |-):
sql: """SQL _trim("\n", ">")
    SELECT id, email
      FROM users
     WHERE active = true
    SQL

# Fold paragraphs (YAML's >+):
prose: """DOC _fold_paragraphs()
    The quick brown fox
    jumps over the lazy dog.

    Sphinx of black quartz,
    judge my vow.
    DOC

# Literal heredoc — no escape processing:
regex: '''RE
    ^[A-Za-z_][A-Za-z0-9_]*$
    RE

Numbers — base prefixes, no inference

port:       8080            # always decimal integer
mask:       0xFF_00_00      # hex with digit underscores
perms:      0o644           # octal
flags:      0b1010_0101     # binary
ratio:      0.42            # decimal float
hex_float:  0x1.8p3         # hex float (= 12.0)
sentinel:   nan             # also: inf, -inf

ready:      true
shutdown:   false           # not "no", not "off"

Dates & times — first-class

deployed: 2026-04-24T09:30:00-04:00   # offset datetime
window:   2026-04-24T09:30:00         # local datetime
release:  2026-04-24                  # local date
cutover:  09:30:00                    # local time

Lists & tables — block and flow

# block list with "+" — one character, never doubled brackets
servers:
  + name: "web1"
    port: 443
  + name: "web2"
    port: 443

# flow list (inline)
regions: ["us-east-1", "eu-west-1", "ap-south-1"]

# block table (indent)
database:
  host: "db.internal"
  port: 5432

# flow table (inline)
cache: { host: "redis", db: 0 }

Front matter — for document metadata

+++
app_name:     "myservice"
doc_version:  "1.2.3"
updated:      2026-04-23
+++

# the actual document body starts here
database:
  host: "db.internal"
  port: 5432

Polymorphic root — list, table, scalar, or empty

# all three are valid DMS documents:

# 1. a table
title: "production"

# 2. a bare list
+ "apples"
+ "oranges"

# 3. a single scalar
42

Unicode keys — bare, no quoting required

résumé:            "ada.pdf"
こんにちは:         "hello"
"path with space": "/etc/dms.conf"   # quote only the unusual

Comments survive round-trip

Configuration files are documentation. A comment on port: 8080 # raised after the LB change in 2024-Q4 exists so someone in 2026 can read it. If the first formatter or deploy template renderer drops it, the documentation was a lie.

DMS makes comments first-class AST nodes attached to the value tree at one of three positions: leading (the line above a key), trailing (same line, after the value), and floating (a block-final note before the closing indent). to_dms(parse(source)) walks the tree and writes them back where they belong. Modify the data — rename a key, sort a list, delete a server — and the comments on the still-present nodes travel with them. Round-trip is byte-stable on the second pass.

Format Comments survive parse → modify → re-emit?
JSONNo — no comments in spec
JSON5No — every library drops them
YAMLruamel.yaml only (Python), opt-in, slower
TOMLtoml-edit crate only (Rust), separate value type
DMSYes — every reference parser, by spec

Fast, too

DMS was designed for readability, but the parsers turn out to be quick. Most ports clear a 50 000-entry flat map (~700 KB) in under 50 ms — and they do it while preserving comment attachments and literal-form metadata that the comparison parsers throw away.

Language DMS JSON YAML TOML
Python16 ms20 ms853 ms399 ms
C 16 ms11 ms19 ms 7537 ms
Zig 17 ms19 ms409 ms197 ms
Rust 20 ms12 ms85 ms 66 ms
Go 32 ms24 ms6356 ms90 ms
Perl 44 ms28 ms53 ms 2584 ms
Node 96 ms29 ms123 ms337 ms

Startup-subtracted best-of-N, 50 000-key flat map. DMS comes out ahead of YAML and TOML in every language. Bench scripts live in dms-tests.

Ready?

The spec is short, by design.