====== Daten & Serialisierung ======
Daten in eine Datei, über ein Netz oder in eine Konfiguration bringen — und wieder zurück. Diese Seite ordnet die Formate ''std.yaml'', ''std.json'', ''std.pack'', ''std.base64'' und ''std.xml'' ein, zeigt für jedes den Arbeitsweg und benennt, **was in lyxc 1.0.16F tatsächlich trägt** und was nicht.
→ [[lyx_-_programmiersprache:units:yaml|std.yaml]] · [[lyx_-_programmiersprache:units:json|std.json]] · [[lyx_-_programmiersprache:units:pack|std.pack]] · [[lyx_-_programmiersprache:units:base64|std.base64]] · [[lyx_-_programmiersprache:units:conv|std.conv]]
----
===== 1. Welches Format für welchen Zweck? =====
^ Aufgabe ^ Format ^ Unit ^ Zustand ^
| Konfigurationsdatei, von Menschen gepflegt | YAML | ''std.yaml'' | **einsatzfähig**, auch verschachtelt — Struktur nicht im Betrieb erweitern |
| Eigenes Binärformat, Datei oder Protokoll | Pack | ''std.pack'' | **einsatzfähig** |
| Binärdaten in Text verpacken (URLs, Header, Data-URLs) | Base64 | ''std.base64'' | **einsatzfähig** in beide Richtungen |
| JSON erzeugen, prüfen, Werte maskieren | JSON | ''std.json'' | teilweise — siehe Abschnitt 4 |
| Zahlen als Bytes, Endianness, Hex/Binär | — | ''std.conv'' | einsatzfähig |
| XML schreiben, prüfen, flach lesen | XML | ''std.xml'' | einsatzfähig — kein Dokumentbaum, siehe Abschnitt 7 |
Zwei Grundentscheidungen vorweg:
* **Menschen oder Maschinen?** Was ein Mensch bearbeitet, gehört nach YAML. Was nur zwischen zwei Programmen fließt, gehört in ein Binärformat — kompakter, schneller, ohne Parser-Überraschungen.
* **Selbst festlegen oder fremdes Format bedienen?** Wer das Format bestimmt, nimmt ''std.pack''. Wer JSON von einer fremden API bekommt, hat in 1.0.16F mehr Arbeit, als ihm lieb ist (Abschnitt 4).
----
===== 2. YAML — Konfiguration =====
''std.yaml'' hält ein Dokument als Handle (''int64'') und greift über Pfad-Zeichenketten darauf zu. Lesen, Ändern, Schreiben — alles über dasselbe Handle.
import std.io;
import std.alloc;
import std.yaml;
fn main(): int64 {
var doc: int64 := ParseString("name: server\nport: 8080\ndebug: true\n"c);
if (doc == 0) { PrintLn("Parse fehlgeschlagen"); return 1; }
PrintLn(GetString(doc, "name"c, "?"c)); // server
PrintLn(IntToStr(GetInt(doc, "port"c, 0))); // 8080
PrintBoolLn(GetBool(doc, "debug"c, false)); // true
PrintBoolLn(HasPath(doc, "fehlt"c)); // false
SetInt(doc, "port"c, 9090);
PrintLn(IntToStr(GetInt(doc, "port"c, 0))); // 9090
var out: int64 := alloc(4096);
var n: int64 := WriteString(doc, out as pchar, 4096);
Print("geschrieben: "); PrintLn(IntToStr(n)); // 36
var s: pchar := out as pchar;
PrintLn(s);
// name: server
// port: 9090
// debug: true
return 0;
}
==== Der Werkzeugkasten ====
^ Lesen ^ Schreiben ^ Prüfen ^
| ''GetString'', ''GetInt'', ''GetFloat'', ''GetBool'' | ''SetString'', ''SetInt'', ''SetFloat'', ''SetBool'' | ''HasPath'', ''GetType'' |
| ''GetArray'', ''GetArrayLen'', ''GetObject'', ''GetKeys'' | ''SetArray'', ''SetObject'', ''DeleteKey'' | ''IsValidKey'', ''NeedsQuoting'' |
| ''LoadFile(path)'' | ''SaveFile(doc, path)'', ''WriteFile'', ''WriteString'' | — |
Jede ''Get''-Funktion nimmt einen **Vorgabewert** entgegen. Das ist bequem und gefährlich zugleich: Ein fehlender Schlüssel ist damit nicht vom vorhandenen Schlüssel mit demselben Wert zu unterscheiden. Wo der Unterschied zählt, vorher ''HasPath'' fragen.
==== Verschachtelte Pfade ====
Ein Punktpfad greift in die Struktur:
var doc: int64 := LoadFile("cfg.yaml"c);
PrintLn(GetString(doc, "server.host"c, "?"c)); // localhost
PrintLn(IntToStr(GetInt(doc, "server.port"c, 0))); // 8080
PrintLn(IntToStr(GetArrayLen(doc, "users"c))); // 2
SetInt(doc, "server.port"c, 9090); // ändert den vorhandenen Wert
SaveFile(doc, "cfg.yaml"c);
> **Zwei Reste beim Schreiben und Prüfen:**
>
> * ''SetString(doc, "server.name"c, "prod"c)'' für einen **neuen** Schlüssel unter einem vorhandenen Knoten hängt einen **zweiten** ''server:''-Block an das Dateiende, statt in den bestehenden zu schreiben. Vorhandene Schlüssel zu ändern arbeitet dagegen korrekt.
> * ''GetType(doc, "server"c)'' liefert für einen Knoten weiterhin ''0''.
>
> Praktische Folge: die Struktur einer Konfiguration beim Schreiben nicht erweitern — Schlüssel, die später gesetzt werden sollen, von Anfang an in der Datei anlegen. Geprüft mit lyxc 1.0.16F, stdlib-Stand ''80479041''.
> ([[https://github.com/SEOLizer/LyX-Compiler/issues/1312|Issue #1312]])
==== Datei rein, Datei raus ====
var doc: int64 := LoadFile("config.yaml"c);
if (doc == 0) { PrintLn("Datei nicht lesbar"); return 1; }
SetString(doc, "stand"c, "produktiv"c);
if (!SaveFile(doc, "config.yaml"c)) { PrintLn("Schreiben fehlgeschlagen"); return 1; }
''LoadFile'' gibt ''0'' zurück, wenn die Datei fehlt oder nicht lesbar ist — der Rückgabewert ist also **immer** zu prüfen, sonst laufen alle folgenden ''Get''-Aufrufe stumm in ihre Vorgabewerte.
----
===== 3. Pack — eigenes Binärformat =====
''std.pack'' schreibt Werte hintereinander in einen Puffer. Kein Schema, keine Feldnamen, kein Overhead — Schreib- und Lesereihenfolge müssen zusammenpassen, sonst kommt Unsinn heraus.
import std.io;
import std.alloc;
import std.pack;
fn main(): int64 {
var buf: int64 := alloc(256);
var pos: int64 := 0;
pos := pos + PackInt64(buf, pos, -1234567);
pos := pos + PackInt32(buf, pos, -42);
pos := pos + PackBool(buf, pos, true);
pos := pos + PackString(buf, pos, "hallo"c as int64);
Print("Bytes gesamt: "); PrintLn(IntToStr(pos)); // 19
var rp: int64 := 0;
PrintLn(IntToStr(UnpackInt64(buf, rp))); rp := rp + 8; // -1234567
PrintLn(IntToStr(UnpackInt32(buf, rp))); rp := rp + 4; // -42
PrintBoolLn(UnpackBool(buf, rp)); rp := rp + 1; // true
var slen: int64 := ReadVarInt(buf, rp); // Länge zuerst
var sptr: int64 := UnpackString(buf, rp); // dann die Zeichen
var i: int64 := 0;
while (i < slen) { PrintChar(peek8(sptr + i)); i := i + 1; }
PrintLn(""); // hallo
free(buf, 256);
return 0;
}
> **Die ''Pack''-Funktionen geben die Anzahl geschriebener Bytes zurück, nicht die neue Position.** ''pos := PackInt64(buf, pos, x)'' setzt die Position auf 8 zurück, statt sie vorzurücken — die folgenden Werte überschreiben einander, und beim Lesen kommt Unsinn heraus (''-40694658876570247'' statt ''-1234567''). Richtig ist immer:
>
>
> pos := pos + PackInt64(buf, pos, x);
>
==== Feste und variable Breiten ====
^ Funktion ^ Bytes ^ Anmerkung ^
| ''PackInt64'' / ''UnpackInt64'' | 8, little-endian | Standardfall |
| ''PackInt32'' / ''UnpackInt32'' | 4 | Ergebnis **vorzeichenbehaftet** ausgelegt |
| ''PackInt16'', ''PackInt8'' | 2 / 1 | für kompakte Felder |
| ''PackBool'' | 1 | |
| ''PackFloat64'' / ''PackFloat32'' | 8 / 4 | |
| ''WriteVarInt'' / ''ReadVarInt'' | 1–10 | kleine Zahlen kosten ein Byte |
| ''PackString'' | VarInt-Länge + Zeichen | ''VarIntSize'' liefert die Präfixgröße |
| ''PackNull'' / ''IsNull'' | 1 | Marker ''255'' |
| ''PackArrayStart'' / ''UnpackArrayStart'' | VarInt | Elementzahl voranstellen |
''UnpackString'' liefert einen Zeiger auf die **Zeichen** (das Längenpräfix ist übersprungen). Die Länge muss man sich vorher mit ''ReadVarInt'' an derselben Position holen — sie steht nicht am Ende und die Zeichen sind **nicht** nullterminiert.
==== Ein Datensatz mit Kopf ====
// Schreiben: erst die Anzahl, dann die Elemente
var pos: int64 := 0;
pos := pos + PackArrayStart(buf, pos, 3);
var k: int64 := 0;
while (k < 3) {
pos := pos + PackInt32(buf, pos, werte[k]);
k := k + 1;
}
// Lesen: Anzahl holen, Position um die Präfixgröße vorrücken
var anzahl: int64 := UnpackArrayStart(buf, 0);
var rp: int64 := VarIntSize(anzahl);
Für Formate, die andere Programme lesen sollen, gehört an den Anfang **immer** eine Version: ''PackInt16(buf, 0, 1)''. Ohne sie lässt sich ein Format später nicht mehr erweitern, ohne alle alten Dateien unlesbar zu machen.
----
===== 4. JSON =====
''std.json'' arbeitet intern mit einem **pipe-separierten** Zwischenformat: ein JSON-Array wird zu ''1|2|3'' und zurück. Einen Objektzugriff („hole ''user.name''") gibt es nicht.
Was nachweislich trägt:
import std.io;
import std.alloc;
import std.json;
fn main(): int64 {
PrintBoolLn(isValidJSON("{\"a\":1}"c)); // true
PrintBoolLn(isValidJSON("[1,2,3]"c)); // true
PrintBoolLn(isValidJSON("nonsense"c)); // false
var d1: int64 := alloc(1024);
var rc: int64 := parseArray(d1 as pchar, "[1,2,3]"c); // rc = ERR_JSON_OK
var pipe: pchar := d1 as pchar;
PrintLn(pipe); // 1|2|3
var d2: int64 := alloc(1024);
var json: pchar := stringify(d2 as pchar, "1|2|3"c);
PrintLn(json); // ["1","2","3"]
var d3: int64 := alloc(1024);
var esc: pchar := JSONEscape(d3 as pchar, "a\"b"c);
PrintLn(esc); // "a\"b"
return 0;
}
* ''isValidJSON'' prüft Wohlgeformtheit — brauchbar als Eingangsprüfung, bevor man selbst Hand anlegt.
* ''parseArray'' und ''toArray'' liefern das Pipe-Format; der Rückgabewert von ''parseArray'' ist ein **Fehlercode** (''ERR_JSON_OK'' = 0), nicht die Elementzahl.
* ''stringify'' und ''serializeArray'' gehen den Weg zurück. ''stringify'' setzt alle Elemente als **Zeichenketten** — aus ''1|2|3'' wird ''["1","2","3"]'', nicht ''[1,2,3]''.
* ''JSONEscape'' maskiert korrekt und ist das Werkzeug der Wahl, wenn man JSON selbst zusammensetzt.
==== JSON erzeugen, ohne den Parser zu brauchen ====
Ausgehende Anfragen sind der einfachere Fall: Man baut den Text selbst und maskiert nur die Werte.
var esc: int64 := alloc(512);
var sicher: pchar := JSONEscape(esc as pchar, benutzername);
// {"name": , "aktiv": true}
==== Antworten auswerten ====
Für den Weg hinein — fremdes JSON in eigene Werte — fehlt in 1.0.16F der Objektzugriff. Drei gangbare Wege:
- **Format wählen, wenn man kann.** Bei einer eigenen API auf ''std.pack'' oder YAML ausweichen.
- **Gezielt suchen statt vollständig parsen.** Mit ''std.string'' die Position von ''"feld":'' suchen und den Wert bis zum nächsten Komma oder ''}'' herausschneiden. Robust genug für bekannte, stabile Antworten — kein Ersatz für einen Parser bei fremden Daten.
- **Arrays über das Pipe-Format.** Reine Wertelisten (''[1,2,3]'') lassen sich mit ''toArray'' verarbeiten.
----
===== 5. Base64 =====
Binärdaten durch Kanäle bringen, die nur Text vertragen: HTTP-Header, URLs, Data-URLs, Mail-Anhänge.
import std.io;
import std.alloc;
import std.base64;
fn main(): int64 {
var out: int64 := alloc(256);
var n: int64 := Encode("Hallo Welt"c, out as pchar);
var s: pchar := out as pchar;
Print("Länge: "); PrintLn(IntToStr(n)); // 16
PrintLn(s); // SGFsbG8gV2VsdA==
PrintBoolLn(IsValid(s)); // true
return 0;
}
^ Funktion ^ Zweck ^
| ''Encode'' / ''EncodeBytes'' | Standard-Alphabet; ''EncodeBytes'' auch für Daten mit Nullbytes |
| ''EncodeUrlSafe'' | ''-'' und ''_'' statt ''+'' und ''/'' — für URLs und JWT |
| ''EncodedLen'' / ''DecodedLen'' | Puffergröße **vorher** ausrechnen |
| ''IsValid'' / ''IsValidChar'' / ''HasValidPadding'' | Eingangsprüfung |
| ''AddPadding'' / ''StripPadding'' | ''=''-Auffüllung angleichen |
| ''EncodeBasicAuth'' | fertiger Wert für einen ''Authorization''-Header |
| ''EncodeDataUrl'' | ''data:image/png;base64,…'' für HTML/SVG |
Der Rückweg arbeitet ebenso:
var back: int64 := alloc(256);
var m: int64 := Decode("SGFsbG8gV2VsdA=="c, back as pchar, 256);
var t: pchar := back as pchar;
PrintLn(t); // Hallo Welt (m = 10)
Puffer immer über ''EncodedLen''/''DecodedLen'' bemessen und ein Byte für die Nullterminierung zugeben — Base64 wächst um ein Drittel.
----
===== 6. Zahlen und Bytes — std.conv =====
Die Brücke zwischen Zahlen und ihrer Byte-Darstellung. Unverzichtbar, sobald ein fremdes Binärformat oder ein Netzprotokoll im Spiel ist.
import std.io;
import std.conv;
fn main(): int64 {
PrintLn(IntToHex32(255)); // 000000FF
PrintLn(IntToStr(SwapEndian32(1))); // 16777216
PrintLn(IntToStr(ParseHex("FF"c))); // 255
return 0;
}
^ Gruppe ^ Funktionen ^
| Darstellung | ''IntToHex8/16/32/64'', ''IntToBin8/16/32/64'', ''IntToOct'', ''IntToStrWithComma'' |
| Zurücklesen | ''ParseHex'', ''ParseBin'', ''ParseOct'' |
| Byte-Reihenfolge | ''SwapEndian16/32/64'', ''GetByte'', ''SetByte'' |
| Bits | ''GetBit'', ''SetBit'', ''ClearBit'', ''ToggleBit'', ''ExtractBits'', ''InsertBits'' |
| Breiten | ''SignExtend8/16/32'', ''ZeroExtend8/16/32'', ''ClampUint8/16/32'' |
| Ausrichtung | ''AlignUp'', ''AlignDown'', ''IsAligned'' |
''SwapEndian32'' ist die Antwort auf Netzwerk-Byte-Reihenfolge: Werte aus Paket-Headern kommen big-endian, Lyx rechnet little-endian. ''SignExtend8/16/32'' braucht, wer schmale vorzeichenbehaftete Felder aus einem Puffer liest — ohne den Schritt wird aus ''-1'' die Zahl ''255''.
----
===== 7. XML =====
''std.xml'' schreibt und prüft XML; zum Lesen gibt es eine flache Array-Darstellung statt eines Baums.
import std.io;
import std.alloc;
import std.xml;
fn main(): int64 {
var doc: pchar := "server8080"c;
PrintBoolLn(IsValid(doc)); // true
Print("Elemente: "); PrintLn(IntToStr(CountElements(doc))); // 3
var esc: int64 := alloc(256);
EscapeText("a
// Inhalt
return 0;
}
^ Aufgabe ^ Funktion ^
| Prüfen und zählen | ''IsValid'', ''CountElements'' |
| Maskieren | ''EscapeText'', ''EscapeAttribute'' |
| Schreiben | ''WriteDeclaration'', ''WriteElement'', ''WriteDocument'' |
| Lesen | ''ParseString'', ''XMLToArray'', ''GetArrayEntryName'', ''GetArrayEntryText'' |
| Formatieren | ''PrettyPrint'' |
* **Einen Dokumentbaum gibt es nicht.** ''XMLToArray'' liefert eine flache Liste von Name/Text-Paaren; Verschachtelung und Attribute gehen dabei verloren. Für tief strukturiertes XML ist das zu wenig.
* ''PrettyPrint'' übernimmt vorhandene Zeilenumbrüche mit — die Ausgabe enthält den Textinhalt samt Umbruch vor dem schließenden Tag.
* ''EscapeText'' und ''EscapeAttribute'' sind auch dann das richtige Werkzeug, wenn man die Ausgabe von Hand zusammensetzt.
----
===== 8. Muster, die überall gelten =====
==== Puffer bemessen, nicht raten ====
Jede Serialisierungsfunktion schreibt in einen Puffer, den der Aufrufer stellt. Es gibt **keine** Größenprüfung — ein zu kleiner Puffer überschreibt Nachbarspeicher. Die Größe kommt entweder aus einer Rechenfunktion (''EncodedLen'', ''VarIntSize'', ''StringPackSize'') oder aus einer bewusst gesetzten Obergrenze, die man beim Schreiben mitprüft.
==== Ergebnisse als Zeichenkette ausgeben ====
var s: pchar := puffer as pchar;
PrintLn(s); // richtig
PrintLn(satz.name); // pchar-FELD: gibt die ADRESSE aus
Ein ''pchar''-**Feld** eines Structs geht als Zahl durch die Ausgabe — der Umweg über eine Variable hilft. Die Typumwandlung im Argument (''%%PrintLn(puffer as pchar)%%'') war bis lyxc 1.0.15G ebenso betroffen und arbeitet seit **1.0.16F** korrekt; ''PrintStrLn'' in keinem der Fälle.
==== Drei Fehlerarten auseinanderhalten ====
^ Fehler ^ Erkennbar an ^ Reaktion ^
| Eingabe fehlt | ''LoadFile'' → 0, ''ParseString'' → 0 | abbrechen, melden |
| Eingabe ist kaputt | ''isValidJSON'' → false, Fehlercode ≠ 0 | ablehnen, nicht raten |
| Feld fehlt | ''HasPath'' → false | Vorgabewert bewusst setzen |
Der dritte Fall ist der gefährliche: Er sieht wie ein Erfolg aus. Wer ''GetInt(doc, "port"c, 0)'' schreibt und nie ''HasPath'' fragt, startet seinen Dienst irgendwann auf Port 0 und sucht den Fehler woanders.
==== Freigeben ====
^ Angelegt mit ^ Freigegeben mit ^
| ''alloc(n)'' | ''free(ptr, n)'' — dieselbe Größe |
| ''ParseString'', ''LoadFile'' (YAML-Handle) | über die Lebensdauer des Programms halten oder Dokument neu laden |
----
===== 9. Zustand auf einen Blick (lyxc 1.0.16F) =====
^ Unit ^ Kodieren/Schreiben ^ Dekodieren/Lesen ^
| ''std.yaml'' | ✅ ''WriteString'', ''SaveFile'' · ⚠️ neuer Schlüssel unter vorhandenem Knoten hängt einen zweiten Block an | ✅ auch verschachtelte Pfade |
| ''std.pack'' | ✅ | ✅ |
| ''std.base64'' | ✅ | ✅ |
| ''std.json'' | ✅ ''stringify'', ''JSONEscape'' | ⚠️ nur Arrays über Pipe-Format |
| ''std.conv'' | ✅ | ✅ |
| ''std.xml'' | ✅ ''WriteDocument'', ''EscapeText'' | ⚠️ flache Liste, kein Baum, keine Attribute |
----
===== 10. Weiterführend =====
* [[lyx_-_programmiersprache:units:yaml|std.yaml]] · [[lyx_-_programmiersprache:units:json|std.json]] · [[lyx_-_programmiersprache:units:pack|std.pack]] · [[lyx_-_programmiersprache:units:base64|std.base64]] · [[lyx_-_programmiersprache:units:conv|std.conv]] · [[lyx_-_programmiersprache:units:xml|std.xml]]
* [[lyx_-_programmiersprache:guides:netzwerk|Netzwerk-Guide]] — Antworten holen, die hier ausgewertet werden
* [[lyx_-_programmiersprache:sprache:rohspeicher|Rohspeicher: alloc, peek & poke]]
* [[lyx_-_programmiersprache:sprache:std-fehlerkonventionen|Fehlerkonventionen der Standardbibliothek]]
Alle Codebeispiele dieser Seite sind mit lyxc 1.0.16F übersetzt und ausgeführt worden; die angegebenen Ausgaben sind die tatsächlichen.