Antra

Themen

AND - Antra Notation for Data

AND ist das Datenformat fuer serialisierbare Antra-Werte in Antra.

Die Idee dahinter ist einfach: Wenn Antra Werte zwischen Prozessen schicken, auf Platte ablegen oder spaeter cachen soll, dann ist ein sprachnaeheres Format hilfreicher als JSON.

Dieses Kapitel ist die praezise v1-Spezifikation fuer die aktuelle Implementierung in Antra::AND. Es beschreibt den unterstuetzten Wertebereich, die beabsichtigte Lesart und die bewussten Grenzen von v1.

Grundregel: AND ist Antra#

Jedes AND-Dokument ist ein gueltiger Antra-Ausdruck, und als Antra ausgewertet ergibt es genau den Wert, den AND.decode(...) liefert. AND ist also eine Teilmenge der Antra-Literale — es gibt keine eigene Syntax, die in Antra etwas anderes bedeuten wuerde.

Die Richtung ist bewusst einseitig: Nicht jedes Antra-Literal ist AND. Was nur im Quelltext sinnvoll ist (Interpolation, doppelte Map-Keys, Variablen, Ausdruecke), lehnt AND.decode(...) mit einem Fehler ab.

Status#

  • AND ist als Formatname festgelegt.
  • Ausgeschrieben bedeutet der Name Antra Notation for Data.
  • Antra::AND stellt AND.tokenize(...), AND.tokenize(...).!, AND.encode(...), AND.encode(...).!, AND.decode(...) und AND.decode(...).! bereit.
  • System.send(...) und System.receive(...) nutzen AND bereits als Protokoll auf Fork-/Process-Kanaelen.
  • Fuer rohe Zeilenprotokolle ohne AND stehen in Antra::System zusaetzlich send_text/receive_text bereit.

API#

Die aktuelle Moduloberflaeche ist klein gehalten:

import Antra::AND as AND

println(AND.tokenize("%User{name: \"Ada\"}").!)
println(AND.encode(%User{name: "Ada", tags: [:admin, :active]}).!)
println(AND.decode("%{name: \"Ada\", active: true}").!)

AND.tokenize(...), AND.encode(...) und AND.decode(...) liefern Result-Werte. Mit dem postfixen .! (z.B. AND.decode(text).!) entpackst du direkt und wirfst bei Fehlern wie ueblich einen Interpreter-Fehler.

AND.tokenize(...) ist vor allem fuer Debugging und Parser-Entwicklung gedacht. AND.decode(...) nutzt diese Tokenisierung intern und baut daraus den eigentlichen Laufzeitwert.

Zielsetzung#

AND soll vier Dinge gleichzeitig leisten:

  • sprachnahe Darstellung normaler Antra-Daten
  • gute Lesbarkeit fuer Menschen beim Debuggen
  • stabile Serialisierung fuer IPC und Persistenz
  • klar abgegrenzte Fehlermeldungen fuer nicht serialisierbare Runtime-Werte

Wichtig ist die bewusste Abgrenzung: AND ist ein Datenformat, kein allgemeiner Dump der gesamten Runtime.

Nicht-Ziele#

AND v1 ist absichtlich nicht fuer diese Dinge gedacht:

  • Funktionen, Lambdas oder Closures serialisieren
  • laufende Prozesse serialisieren
  • offene Datei- oder Stream-Handles serialisieren
  • beliebige native Opaque Values verlustfrei durchreichen
  • beliebige Interna der Runtime sichtbar machen

Wenn ein Wert eine laufzeitgebundene Ressource repraesentiert, soll AND.encode(...) dafuer einen klaren Fehler liefern statt eine halbgueltige Darstellung zu erzeugen.

Grundprinzip#

AND beschreibt nur Werte, die als reine Daten gelten.

Dazu gehoeren in v1:

  • Zahlen
  • rationale Zahlen
  • Strings
  • true, false, undef
  • Keywords
  • Arrays
  • Maps
  • Sets und Tagged Sets
  • Ranges (1..10, 'a'..'z')
  • Tagged Arrays (auch in der Kurzform 30sec)
  • Tagged Maps

Nicht dazu gehoeren in v1:

  • Process
  • FileHandle
  • ByteString, solange dafuer keine explizite Literalsyntax festgelegt wurde
  • Funktionen
  • andere opake native Werte

Allgemeine Regeln#

Textformat#

  • AND ist ein textuelles Format.
  • Das Encoding ist UTF-8.
  • Fuehrende und nachfolgende Leerzeichen um den Gesamtwert sind erlaubt.
  • Leerzeichen, Zeilenumbrueche und Einrueckung sind ausserhalb von Strings frei fuer Lesbarkeit nutzbar.
  • # leitet ausserhalb von Strings einen Kommentar bis zum Zeilenende ein; er zaehlt wie Leerraum. Innerhalb eines Strings ist # normaler Text. AND.encode(...) schreibt keine Kommentare.
# Bibliotheken fuer das Spiel
%{raylib: "6.2",   # Grafik
  sdl2: "2.32.10"} # nur fuer Windows-Builds

Ein Wert pro Dokument#

Ein AND-Dokument enthaelt genau einen Top-Level-Wert.

Gueltig sind also zum Beispiel:

42
[1, 2, 3]
%User{name: "Ada", age: 36}

Nicht gueltig waere dagegen ein Text mit zwei getrennten Top-Level-Werten hintereinander.

Deterministische Ausgabe#

AND.encode(...) soll fuer denselben serialisierbaren Wert immer dieselbe Zeichenfolge erzeugen.

Das ist wichtig fuer:

  • reproduzierbare Tests
  • Debugging
  • spaetere Vergleiche
  • moegliche Caches oder Hashing

Wertebereich in v1#

Zahlen#

Zahlen stehen in ihrer Antra-Form, mit optionalem - davor:

0
-7
42
3.5
1_000_000
0xFF_FF
0b1010_0101
0o755
1e300
1.5e-3
  • Ganzzahlen dezimal, hexadezimal (0x/0X), binaer (0b/0B) oder oktal (0o/0O).
  • _ darf zur Gliederung zwischen zwei Ziffern stehen (1_000, 0xFF_FF, 3.141_59), nicht am Anfang, am Ende oder doppelt.
  • Eine Dezimalzahl ist wie im Antra-Quelltext eine exakte rationale Zahl: 3.5 ist 7/2, 0.1 ist 1/10 — keine Gleitkommazahl.
  • Zahlen mit Exponent (1e300, 6.02e23, 1.5e-3, 2E+10) sind ebenfalls exakt, solange sie in 64 Bit passen (1e3 ist 1000, 1.5e-3 ist 3/2000), sonst Gleitkommazahlen. 5em ist wie in Antra der Tagged Scalar %em[5].
  • Ganzzahlen jenseits von 64 Bit und Dezimalzahlen mit mehr als 18 Stellen vor oder nach dem Punkt werden, wie in Antra, zu Gleitkommazahlen.

AND.encode(...) schreibt Ganzzahlen immer dezimal ohne _, Brueche in Bruchform (7/2) und Gleitkommazahlen bei Bedarf mit Exponent (1e+300; Unendlich und NaN haben kein Literal und sind ein Fehler) — 0xFF und 1_000 sind Schreibweisen fuer Menschen, die der Wert selbst nicht kennt.

Rationale Zahlen#

Rationale Zahlen stehen als Zaehler/Nenner — in Antra ist das die Division zweier Ganzzahlen und ergibt denselben Bruch:

1/2
3/4
-7/9

Die Ausgabe soll normalisiert sein. Das heisst insbesondere:

  • kein gemeinsamer Teiler in Zaehler und Nenner
  • negatives Vorzeichen hoechstens am Zaehler
  • Nenner nie 0

Strings#

Strings stehen in doppelten Quotes mit denselben Escapes wie in Antra: \n, \r, \t, \", \\, \$ und \u{HEX} (Unicode-Codepoint).

"hello"
"line\nbreak"
"quote: \"x\""
"\u{3b1} und \u{1F600}"
"Preis: \$name bleibt Text"

Ein $ vor einem Namen oder vor { wuerde in Antra interpolieren ("hi $name"). Daten interpolieren nicht: AND verlangt dort \$ und lehnt ein ungeschuetztes $name oder ${...} mit einem Fehler ab. Ein $, dem etwas anderes folgt ("5 $", "$5"), ist auch in Antra nur Text. Andere Backslash-Folgen (\d, \x) sind kein AND.

Einfach gequotete Strings sind wie in Antra roh: kein Escape ausser \' fuer ein Apostroph, ein \ bleibt stehen.

'C:\Users\ada'
'it\'s'

Der Encoder verwendet immer die doppelt gequotete Form mit vollem Escaping (auch \$ vor Namen) — bewusst unabhaengig davon, wie viele Anfuehrungszeichen der Inhalt enthaelt. AND ist ein Serialisierungsformat: es braucht eine einzige kanonische, stabile String-Darstellung, die der AND-Parser exakt zurueckliest (wie "..." in JSON). Das unterscheidet sich bewusst von der menschlichen Wert-Anzeige (dd(...), geschachtelte Strings in println), die die Quote-Art nach minimalem Escaping waehlt ('...' als Default, "..." wenn der Inhalt Apostrophe enthaelt) — dort zaehlt Lesbarkeit, hier Format-Stabilitaet.

Booleans und undef#

Diese Werte werden als nackte Literale geschrieben:

true
false
undef

Keywords#

Keywords werden mit ihrem normalen Antra-Praefix geschrieben und folgen denselben Zeichenregeln: nach dem : ein Buchstabe oder eines von _ * + - / ? ! . $ % = < > &, danach zusaetzlich Ziffern (aber kein < oder > mehr).

:ok
:error
:user/id
:ready?
:x-model
:<=

AND.encode(...) lehnt ein Keyword ab, das sich so nicht schreiben laesst (etwa eines mit Leerzeichen aus keyword("a b")).

AND behandelt Keywords bewusst als eigenen Datentyp und nicht nur als Strings.

Arrays#

Arrays verwenden die uebliche eckige Klammerform:

[]
[1, 2, 3]
["Ada", true, :admin]

Verschachtelung ist erlaubt:

[1, [2, [3]], %{status: :ok}]

Maps#

Maps schreiben sich wie im Antra-Quelltext mit %{...}. Damit ist jedes AND-Dokument zugleich ein gueltiger Antra-Ausdruck — {...} waere in Antra ein Block, keine Map:

%{}
%{name: "Ada", age: 36}
%{status: :ok, tags: [:admin, :active]}

{name: "Ada"} ohne % ist kein AND (und kein Antra-Ausdruck); AND.decode(...) lehnt es mit einem Hinweis auf %{...} ab.

Fuer v1 gilt folgende Regel fuer Map-Keys:

  • erlaubt sind nur string- und keyword-Keys

Das ist keine Einschraenkung von AND, sondern Antras: Map-Keys sind dort immer Strings oder Keywords. Ein Map-Literal nimmt nur diese Keys an (%{1 => "a"} ist ein Parse-Fehler), und zur Laufzeit wird ein anderer Key zu Text — m[1] und m["1"] sind derselbe Eintrag. Andere Key-Typen kann AND erst abbilden, wenn Antra-Maps sie unterscheiden.

Pragmatisch bedeutet das fuer v1:

  • String-Keys sind erlaubt
  • Keyword-Keys sind erlaubt
  • die Kurzschreibweise name: "Ada" ist nur die lesbare Schreibform fuer einen Keyword-Key (fuer Namen aus Buchstaben, Ziffern und _, optional mit ? oder ! am Ende)
  • jedes andere Keyword schreibt sich :x-model => 1, Strings "display name" => "Ada"
  • "display name": "Ada" ist kein Key: wie in Antra waere unklar, ob ein String- oder ein Keyword-Key gemeint ist; AND.decode lehnt es mit einem Hinweis ab
  • :role: :admin ist kein Key — in Antra ein Syntaxfehler
  • ein Key darf nur einmal vorkommen; Antra liesse den letzten gewinnen, AND lehnt doppelte Keys als Fehler ab
  • andere Key-Typen fuehren bei AND.encode(...) zu einem Fehler

Das haelt die erste Version lesbar und vermeidet unnoetige Parser-Komplexitaet.

Beispiele fuer erlaubte Keys:

%{role: :admin, :x-model => 1, "display_name" => "Ada", "full name" => "Ada L."}

Beispiele fuer v1-nicht erlaubte Keys:

%{42: "answer"}
%{%User{id: 1}: "nested"}

Tagged Values#

Tagged Values sind fuer Antra wichtig und sollen deshalb nicht auf JSON-artige Ersatzstrukturen reduziert werden.

Tagged Maps#

Tagged Maps behalten ihre sichtbare Form:

%User{name: "Ada", age: 36}
%Point{x: 10, y: 20}

Sets und Tagged Sets#

Sets schreiben sich wie in Antra mit spitzen Klammern; doppelte Elemente fallen weg, AND.encode(...) sortiert die Elemente kanonisch:

%<1, 2, 3>
%Seen<:a, :b>

Ranges#

Ranges stehen wie in Antra zwischen zwei Zahlen oder zwei einzelnen Zeichen; ..< schliesst das Ende aus, ..* laesst es offen:

1..10
1..<10
-3..3
'a'..'z'
1..*

Wie in Antra ist ein Array, das nur eine endliche Range enthaelt, die Liste ihrer Werte: [1..3] ist [1, 2, 3], ['a'..'c'] ist ["a", "b", "c"]. Mit weiteren Elementen ([1..3, 5]) oder in einem Tagged Array (%Ok[1..3]) bleibt die Range ein Wert.

AND.encode(...) schreibt Ranges in der inklusiven Form (1..<5 wird 1..4). Abgelehnt werden Ranges mit Schrittweite (range(0, 10, 2)) und ein Array, das nur eine Range haelt — beides hat kein Antra-Literal.

Tagged Arrays#

Tagged Arrays behalten ebenfalls ihre eigene Form:

%Pair[1, 2]
%Some[42]
%Err["kaputt"]

Eine Zahl mit direkt angehaengtem Namen ist wie in Antra die Kurzform eines Tagged Arrays mit einem Element (Tagged Scalar) — praktisch fuer Einheiten in Konfigurationen:

30sec
2.5kg
%{timeout: 30sec, size: 1.5mb}

30sec ist %sec[30], -30sec ist %sec[-30]. Der Name beginnt mit einem Buchstaben und folgt ohne Leerzeichen (30 sec ist kein AND). AND.encode(...) schreibt immer die ausfuehrliche Form %sec[30].

Damit bleibt die Datenform fuer Leser sofort erkennbar und muss nicht ueber Hilfsfelder wie %{"tag" => ..., "data" => ...} rekonstruiert werden.

Nicht serialisierbare Werte#

Folgende Werte sollen in AND v1 einen klaren Fehler ausloesen:

#Process<...>
#FileHandle<...>
|x| -> x + 1

Die Fehlermeldungen folgen aktuell dieser Richtung:

  • der Fehler nennt AND.encode
  • der Fehler nennt den nicht unterstuetzten Werttyp
  • der Fehler versucht nicht, Ressourcen stillschweigend in Strings umzubiegen

Kanonische Ausgabe#

Damit AND als robustes Austauschformat taugt, soll die Encoder-Seite kanonisch ausgeben.

Das betrifft mindestens:

  • Strings mit konsistenter Escape-Darstellung
  • rationale Zahlen in normalisierter Form
  • Maps in stabiler Reihenfolge
  • keine bedeutungslose Formatierungsvarianz in der Standardausgabe

Die wichtigste offene Detailentscheidung fuer v1 ist die Reihenfolge von Map-Eintraegen.

Die Spezifikation fuer v1 legt deshalb fest:

  • AND.encode(...) gibt zuerst alle Keyword-Keys aus
  • danach folgen alle String-Keys
  • innerhalb beider Gruppen wird lexikographisch nach der kanonischen Textdarstellung des Keys sortiert

Damit ist die Ausgabe nicht von der internen Map-Reihenfolge abhaengig.

Warum nicht JSON?#

JSON ist fuer Aussenwelt-Interop weiterhin sinnvoll, aber als Kernformat fuer Antra-Daten ist es zu schmal.

JSON verliert oder verbiegt wichtige Antra-Eigenschaften:

  • Keywords werden zu Strings
  • Tagged Values brauchen Hilfskonstruktionen
  • Maps mit nichttrivialen Keys sind unklar
  • rationale Zahlen haben keine natuerliche Darstellung
  • die Lesart ist weniger sprachnah

AND soll genau diese semantischen Verluste vermeiden.

Warum nicht Bencode?#

bencode ist technisch einfach, aber fuer Antra zu arm als primaeres Sprachformat.

Es ist gut fuer Transporte, aber schlecht fuer:

  • direkte Lesbarkeit
  • sprachnahe Literale
  • Tagged Values
  • Keywords
  • spaetere Erweiterbarkeit ohne Zusatzkonventionen

Beispielwerte#

Die folgenden Beispiele illustrieren den geplanten Umfang von v1.

42
3/4
"hello"
:ok
[1, 2, 3]
%{name: "Ada", age: 36, admin: true}
%User{name: "Ada", tags: [:admin, :active]}
%Ok[%User{id: 1, name: "Ada"}]
[
    %Task{id: 1, status: :open},
    %Task{id: 2, status: :done}
]

Beziehung zu Transport-APIs#

Die aktuelle API trennt weiterhin zwischen Textformat und Transportform:

  • AND.encode/decode arbeiten explizit auf Text
  • System.send/receive verwenden dieses Format intern fuer Fork-/Process-Kanaele

Ein spaeterer Ausbau koennte zusaetzlich niedrigere Raw- oder Binary-Transport-APIs anbieten, ohne die Rolle von AND als Datenformat zu aendern.

Empfohlener Start fuer v1#

Eine kleine erste Version ist besser als eine halb offene General-Loesung.

Deshalb ist dieser Zuschnitt fuer v1 absichtlich eng:

  • Zahlen
  • rationale Zahlen
  • Strings
  • true, false, undef
  • Keywords
  • Arrays
  • Maps mit stabilen Key-Formen
  • Sets und Tagged Sets
  • Ranges
  • Tagged Arrays (auch als Tagged Scalar 30sec)
  • Tagged Maps

Und explizit nicht:

  • Funktionen
  • Process
  • FileHandle
  • beliebige andere Opaque Values

Wenn spaeter weitere Werte dazukommen sollen, sollte das bewusst als v2 oder als klar dokumentierte Erweiterung passieren statt stillschweigend.

Quelle dieser Seite