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.
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.
[[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.
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
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 |
|---|---|---|---|---|
| Python | 16 ms | 20 ms | 853 ms | 399 ms |
| C | 16 ms | 11 ms | 19 ms | 7537 ms |
| Zig | 17 ms | 19 ms | 409 ms | 197 ms |
| Rust | 20 ms | 12 ms | 85 ms | 66 ms |
| Go | 32 ms | 24 ms | 6356 ms | 90 ms |
| Perl | 44 ms | 28 ms | 53 ms | 2584 ms |
| Node | 96 ms | 29 ms | 123 ms | 337 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.
Get DMS in your language
Seven first-party parsers. All hold at 4665 / 4665 on the shared conformance corpus.
dms-rscargo add dms-rs
dms-py + dms-cpip install dms-py
dms-jsnpm install dms-js
go get gitlab.com/flo-labs/pub/dms-go
DMS, DMS-XScpanm DMS
git clone …/dms-c
zig fetch …/dms-zig
git clone …/dms-tests
Comments survive round-trip
Configuration files are documentation. A comment on
port: 8080 # raised after the LB change in 2024-Q4exists 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.ruamel.yamlonly (Python), opt-in, slowertoml-editcrate only (Rust), separate value type