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.
→ std.yaml · std.json · std.pack · std.base64 · std.conv
| 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:
std.pack. Wer JSON von einer fremden API bekommt, hat in 1.0.16F mehr Arbeit, als ihm lieb ist (Abschnitt 4).
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;
}
| 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.
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 zweitenserver:-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 weiterhin0.
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-Stand80479041.
(Issue #1312)
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.
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;
}
DiePack-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 (-40694658876570247statt-1234567). Richtig ist immer:
> pos := pos + PackInt64(buf, pos, x); >
| 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.
// 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.
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.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": <sicher>, "aktiv": true}
Für den Weg hinein — fremdes JSON in eigene Werte — fehlt in 1.0.16F der Objektzugriff. Drei gangbare Wege:
std.pack oder YAML ausweichen.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.[1,2,3]) lassen sich mit toArray verarbeiten.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.
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.
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 := "<config><name>server</name><port>8080</port></config>"c;
PrintBoolLn(IsValid(doc)); // true
Print("Elemente: "); PrintLn(IntToStr(CountElements(doc))); // 3
var esc: int64 := alloc(256);
EscapeText("a<b&c"c, esc as pchar);
var e: pchar := esc as pchar;
PrintLn(e); // a<b&c
var out: int64 := alloc(4096);
WriteDocument(out as pchar, "root"c, "Inhalt"c, "1.0"c, "UTF-8"c);
var o: pchar := out as pchar;
PrintLn(o);
// <?xml version="1.0" encoding="UTF-8"?>
// <root>Inhalt</root>
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 |
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.
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.
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.
| 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.
| 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 |
| 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 |
Alle Codebeispiele dieser Seite sind mit lyxc 1.0.16F übersetzt und ausgeführt worden; die angegebenen Ausgaben sind die tatsächlichen.