Document

Document :: # (opaque)
value : Body -> Value

A Value with no Annotation, which is every Value a source text writes without a (name) before it.

node : Str, List(Value), List(Prop) -> Node

A Node with no Annotation and no children, which is the shape a caller assembling a Document by hand writes most of.

Document : List(Node)

Every node a source text holds, in the order it holds them.

Annotation : [Untyped, Typed(Str)]

(name) in front of a node or a value. Its own type because absent and present-but-empty are different documents, and the second is refused.

Node

:= {
    ty : Annotation,
    name : Str,
    args : List(Value),
    props : List(Prop),
    children : List(Node),
}

One name, the args and props given to it, and the nodes nested under it.

Nominal rather than an alias: a node holds its children, and a record alias cannot mention itself.

Prop : { key : Str, value : Value }

A key and its value. Order is not meaning: two props with one key are one prop, and the last one written wins.

Value : { ty : Annotation, body : Body }

An annotation and a body. Every arg and every prop carries exactly one.

Sign : [Positive, Negative]
Exponent : [NoExponent, Exponent({ sign : Sign, digits : Str })]
Parts : { sign : Sign, integer : Str, fraction : Fraction, exponent : Exponent }

The parts a finite Decimal is held as, named so that every signature mentioning one spells it the same way.

Decimal : [Finite(Parts), Infinite(Sign), NotANumber]

A decimal literal is kept as its digits rather than an F64: the KDL test suite requires 1.23E+1000 to round-trip, and no float type holds that magnitude. #inf, #-inf and #nan are keywords rather than numbers, so nothing parses into their parts, but they fill the same Body.

Body : [
    Text(Str),
    Integer(I128),
    Decimal(Decimal),
    Boolean(Bool),
    Null,
]

Which of the five things a Value is. The part of a Value that is not its Annotation.