Grammatik
Das DXN-Textformat ist durch eine PEG-Grammatik in Aether definiert, der Grammatiksprache von Ichor. Der Parser von dextrin wird daraus erzeugt statt von Hand geschrieben.
Pragmas und Tokens
Dies ist die Grammatik, aus der der Parser von dextrin erzeugt wird: priv/grammar/dxn.aether, wie mit dextrin 0.1.3 veröffentlicht, ohne Kommentare. Namen in Großbuchstaben sind Tokens, längste Übereinstimmung zuerst, bei Gleichstand gewinnt die frühere Deklaration; Namen in Kleinbuchstaben sind Regeln, die per geordneter Auswahl über den Tokens arbeiten.
@grammar "dxn"
@root document
@skip TRIVIA
SPACE := [ \t\r\n,]+
COMMENT := "#" (!"\n" .)* "\n"?
TRIVIA := (SPACE | COMMENT)*
NIL := "nil"
TRUE := "true"
FALSE := "false"
EXPONENT := [eE] [+-]? DIGIT+
FLOAT := ("-"? DIGIT+ (("." DIGIT+ EXPONENT?) | EXPONENT)) | "NaN" | "Infinity" | "-Infinity"
DECIMAL := "-"? DIGIT+ ("." DIGIT+)? "M"
RATIONAL := "-"? DIGIT+ "/" DIGIT+
INTEGER := "-"? DIGIT+
IDENT_START := [_\u{41}-\u{5A}\u{61}-\u{7A}\u{AA} … \u{30000}-\u{3134A}\u{31350}-\u{33479}]
IDENT_CONT := IDENT_START | [\u{30}-\u{39}\u{41}-\u{5A}\u{5F}\u{61}-\u{7A} … \u{E0100}-\u{E01EF}\-?!]
IDENTIFIER := IDENT_START IDENT_CONT* ("/" IDENT_START IDENT_CONT*)?
MAP_KEY := IDENTIFIER ":"
KEYWORD := ":" (IDENTIFIER | STRING)
AT_DISCARD := "@_"
AT := "@"
ESCAPE := "\\" ("\"" | "\\" | "n" | "t" | "r" | "0" | "a" | "b" | "f" | "v" | ("x{" HEX+ "}"))
STRING := "\"" (ESCAPE | (![\"\\] .))* "\""
CHAR := "?" (ESCAPE | "s" | (![ \t\r\n] .))
DATE_SIGIL := "~D[" (!"]" .)* "]"
TIME_SIGIL := "~T[" (!"]" .)* "]"
INSTANT_SIGIL := "~U[" (!"]" .)* "]"
REGEX_SIGIL := "~r/" (("\\" .) | (!"/" .))* "/" [imsuxfr]*Die Reihenfolge der Deklarationen leistet hier echte Arbeit. NIL, TRUE und FALSE stehen vor IDENTIFIER und gewinnen so den Gleichstand. MAP_KEY verschmilzt einen Namen mit seinem Doppelpunkt zu einem Token, damit name:value ohne Leerzeichen nie als Name mit folgendem Keyword :value gelesen wird. AT_DISCARD schlägt AT durch seine Länge, also ist @_ immer ein Verwerfen und nie ein eigenes Tag namens _.
Regeln
document := header? value TRIVIA?
header := "@dxn" version:STRING
value := discard* value_body
discard := AT_DISCARD value
value_body := nil | boolean | number | string | char | keyword | symbol
| list | tuple | map_lit | struct_lit | set_lit
| sigil | tag_form
nil := NIL
boolean := TRUE | FALSE
number := DECIMAL | RATIONAL | FLOAT | INTEGER
string := STRING
char := CHAR
symbol := IDENTIFIER
keyword := KEYWORD
list := "[" value* "]"
tuple := "{" value* "}"
set_lit := AT "{" value* "}"
map_lit := "%" "{" map_entry* "}"
map_entry := (short_key:MAP_KEY short_val:value) | (arrow_key:value "=>" arrow_val:value)
struct_lit := "%" name:IDENTIFIER (keyed:struct_keyed | positional:struct_positional)
struct_keyed := "{" map_entry* "}"
struct_positional := "[" value* "]"
sigil := DATE_SIGIL | TIME_SIGIL | INSTANT_SIGIL | REGEX_SIGIL
tag_form := AT name:IDENTIFIER valueDie Sigil-Tokens erfassen ihren Inhalt als Ganzes; Datum, Uhrzeit und reguläre Ausdrücke werden nach dem Parsen von den Parsern der Host-Sprache geprüft. Weil "@dxn" überall im Dokument ein längeres Token ist als AT, nicht nur am Anfang, kann @dxn nie ein eigenes Tag sein.
Wo die Implementierung abweicht
An einigen Stellen akzeptiert dextrin 0.1.3 mehr als die Spezifikation oder legt etwas fest, das sie offenlässt. Dies sind die portablen Entscheidungen:
| Spezifikation | dextrin 0.1.3 | Schreibweise |
|---|---|---|
~U[…] ist nur UTC, mit Z am Ende | Akzeptiert einen Offset und rechnet nach UTC um | Immer Z; @datetime für Offsets |
@datetime braucht einen Offset ungleich UTC | Akzeptiert Z und +00:00 und schreibt den Wert als Zeitstempel zurück | ~U[…] für UTC |
| Die Kopfzeile nennt die Formatversion | Jede Versionszeichenkette wird ungeprüft akzeptiert | @dxn "1.0" |
| Binäre Structs sind positionell, in Schema-Reihenfolge | Ohne Schema wird ein Struct mit Schlüsseln in Quellreihenfolge geschrieben und verliert seine Namen: %Point{y: 2, x: 1} kommt als %Point[2 1] zurück | Positionelle Form, oder mit geladenem Schema kodieren |
| Tag 29 verweist auf „das n-te markierte Element“ | Nummeriert markierte Elemente, wenn sie fertig dekodiert sind, innerste zuerst; die registrierte CBOR-Erweiterung nummeriert sie beim Öffnen | Teilen abschalten für Dateien, die andere CBOR-Werkzeuge lesen |