Regular enough for a pacemaker

kaiv’s grammar is regular — data, schema, everything the runtime touches. On recursive formats nobody’s data needed, structure on the other side of the equals sign, and why all of kaiv processing is mere scanning.


Here is a 100-kilobyte JSON document meeting a standard parser:

$ python3 -c 'import json; json.loads("[" * 100000 + "]" * 100000)' 2>&1 | tail -1
RecursionError: Stack overflow (used 8156 kB) while decoding a JSON array from a unicode string

One hundred kilobytes of input; eight megabytes of stack, and then death. The input is degenerate, but the mechanism that died is not — it is the ordinary mechanism, doing what the format requires. JSON’s grammar is recursive: a value may be an object of values which may be objects of values, all the way down. A parser for a recursive grammar must carry a stack, and the stack’s depth is decided by the shape of the input. Node happens to survive this particular file, by spending even more memory from a bigger budget. The budget is the point: for every recursively-defined format, peak memory is a function of what the data looks like. You cannot state the bound until you’ve seen the document.

Now say that sentence in a design review for a pacemaker. Safety-critical runtimes — MISRA-C in automotive, DO-178C in avionics, IEC 62304 in medical devices — want statically bounded memory, no dynamic allocation, no recursion. A grammar whose memory cost depends on input shape fails that bar as a grammar, before a single line of parser code is written. This is why there is no JSON parser in an airplane’s flight control computer, and it is not because nobody has written a careful one.

kaiv began with this observation, and with a second one that makes it fixable.

Your data is rarely recursive

We use recursively-defined data formats ubiquitously. But look at the data in them: a server config, an invoice, a telemetry frame, a build manifest. Records, with fields, some of which are lists of records — two levels, three, maybe four, and the depth is known when you design the data. Genuinely recursive data — data whose depth is unknowable because the structure refers to itself — is rare. Unless your data is your family tree, or a similar graph shaped by the world rather than by a schema, nothing about it demands a recursive grammar.

JSON gives you one anyway, and makes nesting the default way to structure anything. That default has a quiet cost: nesting is free at authoring time and paid at every parse thereafter, so structures get lazier and deeper than strictly necessary — one more wrapper object, one more level, because the format never pushes back. The grammar is recursive, so the data drifts recursive, so the parsers must be recursive: the costliest property of the format is the one almost no document uses on purpose.

kaiv’s move: take the structure out of the grammar entirely, and put it on the other side of the assignment operator — into the name.

Structure in the namespace

Take a nested document every JSON user recognizes:

$ cat telemetry.json
{
  "device": {
    "id": "PM-4411",
    "firmware": {"version": "3.2.1", "checksum": "roVu2wY"}
  },
  "leads": [
    {"chamber": "RA", "impedance": 512.5},
    {"chamber": "RV", "impedance": 489.0}
  ]
}

Run it through the pipeline to canonical form:

$ kaiv import --json telemetry.json | kaiv build
.!daiv
!str'/device::id=PM-4411
!str'/device/firmware::version=3.2.1
!str'/device/firmware::checksum=roVu2wY
!str'/@leads/0::chamber=RA
!float'/@leads/0::impedance=512.5
!str'/@leads/1::chamber=RV
!float'/@leads/1::impedance=489.0

The braces are gone, and nothing was lost: every level of nesting became a segment of a namepath, on the left of =. /device/firmware::version says “descend into device, then firmware, then project the field version” — the structure that JSON encoded as a tree of delimiters, kaiv encodes as the spelling of each line’s name. Each line is self-contained: type, full address, value. Read any one of them in isolation and you know everything about that value.

The deeper difference is one of perspective. JSON begins at the root — { is the first character you type — and you design top-down: the outermost object first, then its sections, then subsections, until finally, levels down, the values go in last. The structure is conceived first, hollow, hanging in the air; the data arrives afterward to fill it.

kaiv views values as fundamental, and treats them as such. Values are the leaves of the graph, and every leaf is unmistakably identified: the :: projection operator, then its terminal name — the nearest JSON analog is the key in the deepest object holding the value. The entire structure sits to the left of the projection, in the steps (the namespace path); steps, projection, and name together form the namepath, the full address of one value. This is the bottom-up approach: you build the structure out of the data. The values are the foundation, and the containers exist because the values’ addresses pass through them — not the other way around. You never erect a hollow structure and then populate it; every line is a value first, carrying its own place in the whole. That is kaiv’s philosophy in one line of grammar: value on the right of =, identity after ::, structure in the steps.

And now depth is data, not parser state. Here is a pyramid two hundred levels deep:

$ python3 -c 'print("{\"a\":" * 200 + "1" + "}" * 200)' > deep.json
$ kaiv import --json deep.json
.!kaiv

!int
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a::a=1

Two hundred nested objects became one line — a long name, scrolled sideways, assigned once. What used to be two hundred stack frames is now two hundred characters. A scanner reads this line the same way it reads port=8443: left to right, one state machine, zero stack. The cost of depth didn’t disappear — you can see it right there in the file, which is exactly where it belongs: visible, paid by the author who chose it, free for every reader forever after.

Six rules, no stack

What does it take to read a kaiv file? For every line, exactly one of six rules applies:

Rule Test Classification
1 Line is blank Skip
2 Starts with # Comment
3 Starts with // Doc comment
4 Starts with .! or .? Declaration
5 Contains = Content — split on the first =
6 None of the above Metadata annotation

That is the whole classifier, and it is shared by every file in the family — data, schema, type library. Within a line, the shape is fixed: metadata prefix, ', namepath, =, value. A canonical kaiv file is a sequence of lines, each matched by a regular expression — which makes the grammar of the entire file regular. Not “mostly regular”, not “regular except for nesting”: regular. The reader is a DFA over bytes. Its memory is a handful of state variables, the same handful for a ten-line config and a ten-million-line dump, declared at compile time like MISRA always wanted.

Authored kaiv does have conveniences — namespace blocks so you don’t repeat prefixes, variables so you don’t repeat values:

$ cat fleet.kaiv
.!kaiv

.dc=fra1

(/net)
host=api-$.dc.example.com
port=8443
()
$ kaiv build fleet.kaiv
.!daiv
!str'/net::host=api-fra1.example.com
!str'/net::port=8443

But notice where they live: in the authored file, and nowhere else. The build step resolves every variable and unfolds every block into flat, fully-qualified lines. The sugar is the compiler’s business, on a developer workstation with gigabytes to spare — it never crosses over to the reader.

All processing is scanning

Once every line is self-contained, “parsing” stops being a prerequisite for anything. The file is its own index:

$ kaiv import --json telemetry.json | kaiv build > telemetry.daiv
$ grep "'/@leads/" telemetry.daiv
!str'/@leads/0::chamber=RA
!float'/@leads/0::impedance=512.5
!str'/@leads/1::chamber=RV
!float'/@leads/1::impedance=489.0
$ awk -F= '/impedance/ {sum += $2} END {print sum}' telemetry.daiv
1001.5

grep of a namepath prefix is subtree selection — the query you’d write brackets for in a JSON tool is a prefix match on flat lines. awk just summed a field across an array without any kaiv-aware software involved. This isn’t a party trick; it is the same property the certified runtime relies on, showing up in your shell: when structure lives in the spelling of each line, every line-oriented tool ever written — grep, awk, sort, diff, comm, your log shipper — is already a kaiv processor.

The schema scans too

Validation could still have spoiled this — most schema languages are themselves nested documents walked by recursive evaluators. kaiv compiles the schema instead. An authored schema, human-shaped:

$ cat pace.saiv
.!saiv 1 demo/pace

!str
mode=
!int[30,220]
rate_min=
!int[30,220]
rate_max=

compiles to one line per field, in exactly the '-split shape of a data line:

$ kaiv schema pace.saiv > pace.csaiv
$ cat pace.csaiv
.!csaiv 1 demo/pace
!str'::mode=
/^-?[0-9]+$/ ..num [30,220]'::rate_min=
/^-?[0-9]+$/ ..num [30,220]'::rate_max=

(Note !int arriving unfolded into its pattern, span, and range — a type is only ever a constraint over a string, which is the previous article’s whole story.)

Put the compiled schema next to the data it validates:

$ printf '.!kaiv\nmode=DDD\nrate_min=60\nrate_max=130\n' > pace.kaiv
$ kaiv build pace.kaiv > pace.daiv
$ cat pace.daiv
.!daiv
!str'::mode=DDD
!str'::rate_min=60
!str'::rate_max=130
$ kaiv validate pace.daiv pace.csaiv
pass

Line for line, the .csaiv is structurally isomorphic to the .daiv — same classifier, same split, same order, because canonical kaiv keys are strictly ordered. So validation is a parallel scan: two file pointers advancing in lockstep, comparing each data line against its constraint line. No schema tree, no evaluator, no recursion — the schema is not walked, it is read alongside. Levels 0–1 of the format (all scalars and trees) validate this way in constant memory. Level 2 — table constraints like uniqueness and foreign keys — adds one honest O(N) counting pass, and a runtime that implements only Levels 0–1 never executes it and never touches a heap.

The certification boundary

Here is the architecture all of this buys, and it is the oldest trick in safety-critical systems, told about a data format. The kaiv pipeline is .kaiv → compiler → .raiv → denormalizer → .daiv → validator. Everything to the left of .daiv is a build tool: the compiler with its block stack and variable table, the denormalizer resolving references, the schema compiler. Context-free machinery, heaps, hash tables — all of it welcome, because all of it runs on a developer workstation or a CI runner and is validated by testing, not certified.

The target device loads two text files — the .daiv and the .csaiv — and runs two things: the six-rule lexer (a DFA over bytes) and the integrity check (the parallel scan above). No stack, no allocation, memory statically determined by the schema at build time. MISRA-C-certifiable, not by heroic implementation effort, but because there is nothing in the job that needs anything MISRA forbids. The target never sees a namespace block, a variable, a $ reference, or a brace: your sugar was compiled away on the other side of the boundary.

Automotive engineers will recognize the pattern — ECUs have consumed build-tool-generated, certified-runtime-read configuration this way for decades. What kaiv adds is that the interface artifact is not a bespoke binary blob per project but a specified, typed, self-describing text format, with the scanning-only guarantee made by the grammar rather than by a per-project audit.

JSON can’t run in pacemakers or airplane controls. kaiv was designed precisely with this in mind — not adapted to it after the fact; the regular grammar came first, and the format grew around the constraint that the reader must never need more than a scan.

And if your data is a family tree

Then kaiv still holds it — namepaths nest as deep as your data truly is, and the two-hundred-level line above went through the same six rules as everything else. The difference from JSON is not that depth is forbidden; it is that depth is explicit, priced, and paid in the right place. The file shows every level in every name. The author who nests pays in spelling, once, visibly. The reader — every reader, forever, including the one in the pacemaker — still just scans.

That is the theme, and certification is only its sharpest consequence. A regular grammar makes the format greppable, diffable line-by-line, streamable with constant memory, processable by forty years of Unix tools — and, at the far end of the same property, admissible in rooms where “how much memory, worst case?” must have a number for an answer before any code runs at all.


kaiv is an immutable structural type system for data at rest. The User Manual is the accessible tour; the specification defines the six-rule classifier (§ 1.3), the compiled schema, and the parallel-scan validation passes precisely; the playground runs the whole toolchain — including every example above — in your browser.