std.ini

INI-Konfigurationen lesen und schreiben: Sektionen ([server]), Schlüssel-Wert-Zeilen, Kommentare mit # und ;, typisierte Abfragen mit Vorgabewert, Iteration über Sektionen und Schlüssel, Laden und Speichern.

std.string · std.fs · Standard Library

Alle Beispiele dieser Seite sind mit lyxc 1.0.21A übersetzt und ausgeführt; die gezeigten Ausgaben sind echte Programmausgaben.

Einsatz: Anwendungs- und Dienstkonfiguration, Embedded-Systeme, Migrationsskripte — überall dort, wo eine lesbare Schlüssel-Wert-Datei ohne XML- oder JSON-Aufwand genügt.


Wie das Dokument aussieht

Das „Dokument-Handle„ ist eine Kopie des INI-Textes im Speicher, kein Datenmodell. ParseString kopiert die Eingabe, alle weiteren Funktionen durchsuchen diesen Text bei jedem Aufruf von vorn.

Das hat zwei angenehme Folgen:

  • Kommentare, Reihenfolge und Formatierung bleiben erhalten — ein Durchlauf Laden → Ändern → Speichern erhält die Datei so, wie sie war, samt Kopfzeilen und Einrückung.
  • Der Zustand ist einfach: doc as pchar ist der vollständige Text und lässt sich direkt ausgeben.

Und eine unangenehme:

 
Der Dokumentpuffer ist genau so groß wie der Ausgangstext. ParseString legt alloc(len + 1) an; die Änderungsfunktionen schreiben ihr Ergebnis anschließend in denselben Puffer zurück. Wird der Text dabei länger — bei jedem neuen Schlüssel, jeder neuen Sektion, jedem längeren Wert — schreibt die Unit über das Pufferende hinaus in fremden Speicher (#1426).

Nachgemessen: ein Dokument aus [a]\nk=1\n (9 Byte Puffer) enthält nach zwei SetString-Aufrufen 108 Zeichen, der Nachbarblock ist überschrieben.

Bis das behoben ist: Entweder nur lesen, oder den Ausgangstext von vornherein groß genug wählen — etwa indem alle Schlüssel bereits mit Platzhalterwerten in der Datei stehen und SetString sie nur ersetzt (gleich lang oder kürzer).

Lesen

Signatur Beschreibung
ParseString(input: pchar): int64 Kopiert den Text und liefert das Handle; 0 bei leerer Eingabe
LoadFile(path: pchar): int64 Datei einlesen und Handle liefern
GetString(doc, section, key, default_val: pchar): pchar Wert oder Vorgabe
GetInt(doc, section, key, default_val: int64): int64 wie oben, als Ganzzahl
GetBool(doc, section, key, default_val: bool): bool true/1/yes/on gelten als wahr
GetFloat(doc, section, key, default_val: f64): f64 als Fließkommazahl
HasSection(doc, section): bool · HasKey(doc, section, key): bool Bestandsprüfung
GetSectionCount(doc): int64 · GetKeyCount(doc, section): int64 Anzahl

Werte werden getrimmt, und alles ab dem ersten # oder ; gilt als Kommentar — auch mitten in der Zeile.

import std.ini;
import std.alloc;
import std.string;

fn J(b: bool): pchar { if (b) { return "ja"; } return "nein"; }

fn main(): int64 {
    var text: pchar := "; Konfiguration\n[server]\nhost = example.org   ; Kommentar am Zeilenende\nport=8080\ndebug = true\nfaktor = 1.5\n\n[leer]\n"c;
    var doc: int64 := ParseString(text);

    // Typisierte Getter, jeweils mit Vorgabewert
    PrintLn(StrConcat("host:    ", GetString(doc, "server"c, "host"c, "?"c)));
    PrintLn(StrConcat("port:    ", IntToStr(GetInt(doc, "server"c, "port"c, 0))));
    PrintLn(StrConcat("debug:   ", J(GetBool(doc, "server"c, "debug"c, false))));
    PrintLn(StrConcat("fehlt:   ", GetString(doc, "server"c, "gibtsnicht"c, "Vorgabe"c)));
    PrintLn(StrConcat("Sektion fehlt: ", GetString(doc, "andere"c, "host"c, "Vorgabe"c)));

    // Bestand pruefen
    PrintLn(StrConcat("HasSection('server'):    ", J(HasSection(doc, "server"c))));
    PrintLn(StrConcat("HasKey('server','port'): ", J(HasKey(doc, "server"c, "port"c))));
    PrintLn(StrConcat("Sektionen insgesamt:     ", IntToStr(GetSectionCount(doc))));
    PrintLn(StrConcat("Schluessel in [server]:  ", IntToStr(GetKeyCount(doc, "server"c))));
    return 0;
}

host:    example.org
port:    8080
debug:   ja
fehlt:   Vorgabe
Sektion fehlt: Vorgabe
HasSection('server'):    ja
HasKey('server','port'): ja
Sektionen insgesamt:     2
Schluessel in [server]:  4

 
GetString legt für jeden Treffer einen neuen Puffer an und gibt ihn nie frei — eine Freigabefunktion gibt es in der Unit nicht (#1428). 1000 Abfragen kosten 1000 Blöcke. GetInt, GetBool und GetFloat rufen GetString intern auf und verhalten sich genauso.

Für einen langlaufenden Dienst heißt das: Konfiguration einmal beim Start auslesen und die Werte selbst vorhalten, nicht bei jedem Zugriff neu abfragen.

Auflisten

Signatur Beschreibung
GetSections(doc, output: pchar, max_count: int64): int64 schreibt die Sektionsnamen, liefert deren Anzahl
GetKeys(doc, section, output: pchar, max_count: int64): int64 dasselbe für die Schlüssel einer Sektion

Ausgabeformat: Die Namen liegen dicht hintereinander, jeweils nullterminiert — kein festes Raster. Zum nächsten Namen kommt man über p + StrLen(p) + 1.

import std.ini;
import std.alloc;
import std.string;

// Sektions- und Schluesselnamen liegen dicht hintereinander, je nullterminiert
fn Naechster(p: int64): int64 { return p + StrLen(p as pchar) + 1; }

fn main(): int64 {
    // ACHTUNG: Der Dokumentpuffer ist nur so gross wie der Ausgangstext (#1426).
    // Deshalb hier von vornherein genug Text - oder gar nicht erst aendern.
    var doc: int64 := ParseString("[server]\nhost = example.org\nport = 8080\n\n[logging]\nlevel = info\n"c);

    var buf: int64 := alloc(4096);
    var i: int64 := 0;
    while (i < 4096) { poke8(buf + i, 0); i := i + 1; }

    var n: int64 := GetSections(doc, buf as pchar, 16);
    PrintLn(StrConcat("Sektionen: ", IntToStr(n)));
    var p: int64 := buf;
    i := 0;
    while (i < n) { PrintLn(StrConcat("  ", p as pchar)); p := Naechster(p); i := i + 1; }

    i := 0;
    while (i < 4096) { poke8(buf + i, 0); i := i + 1; }
    n := GetKeys(doc, "server"c, buf as pchar, 16);
    PrintLn(StrConcat("Schluessel in [server]: ", IntToStr(n)));
    p := buf; i := 0;
    while (i < n) { PrintLn(StrConcat("  ", p as pchar)); p := Naechster(p); i := i + 1; }

    // Zeilen von Hand bauen - fuer eigene Ausgabe ohne Set...
    var o: pchar := alloc(256) as pchar;
    BuildSectionHeader("db"c, o);       PrintLn(StrConcat("Kopfzeile:  ", o));
    BuildKeyValueLine("user"c, "lyx"c, o); PrintLn(StrConcat("Wertzeile:  ", o));

    // Namen pruefen, bevor sie geschrieben werden
    PrintLn(StrConcat("IsValidSectionName('a b'): ", IntToStr(IsValidSectionName("a b"c) as int64)));
    PrintLn(StrConcat("IsValidKeyName('po rt'):   ", IntToStr(IsValidKeyName("po rt"c) as int64)));
    return 0;
}

Sektionen: 2
  server
  logging
Schluessel in [server]: 2
  host
  port
Kopfzeile:  [db]
Wertzeile:  user=lyx
IsValidSectionName('a b'): 0
IsValidKeyName('po rt'):   0


Ändern und Speichern

Signatur Beschreibung
SetString(doc, section, key, value): void Setzt oder ersetzt; legt Sektion und Schlüssel bei Bedarf an
SetInt · SetBool · SetFloat Hüllen um SetString
DeleteKey(doc, section, key): bool · DeleteSection(doc, section): bool Entfernen
WriteString(doc, output, max_len): int64 Dokument in einen Puffer schreiben
WriteFile(doc, path): bool · SaveFile(doc, path): bool in eine Datei schreiben

Alle Set…-Funktionen geben nichts zurück — ob die Änderung gelungen ist, lässt sich nur über ein anschließendes HasKey oder GetString feststellen. Wegen #1426 sind sie nur sicher, wenn das Ergebnis nicht länger wird als der Ausgangstext.

Die Löschfunktionen verkürzen den Text und sind deshalb unbedenklich; ein Durchlauf Laden → Löschen → Speichern erhält Kommentare und Reihenfolge:

; Kopfkommentar
[s]
k = 1   # Zeilenkommentar

bleibt nach LoadFile + SaveFile unverändert erhalten.


Zeilen und Namen von Hand

Signatur Beschreibung
BuildSectionHeader(section, output): int64 [name]
BuildKeyValueLine(key, value, output): int64 key=value
ParseLine(line, line_len, key_out, value_out): bool Zerlegt eine Schlüssel-Wert-Zeile
IsSectionLine(line, line_len): bool Ist die Zeile ein Sektionskopf?
ParseSectionLine(line, line_len, output): int64 Sektionsnamen aus der Kopfzeile
IsValidSectionName(name): bool · IsValidKeyName(key): bool Namen prüfen — Leerzeichen sind unzulässig
EscapeValue(input, output): int64 · UnescapeValue(input, output): int64 Sonderzeichen im Wert

Diese Bausteine erlauben es, eine INI-Datei zusammenzusetzen, ohne die Set…-Funktionen zu benutzen — der derzeit sicherste Weg, eine Datei neu zu schreiben.

 
EscapeValue maskiert ; und # nicht — obwohl der eigene Parser beim ersten dieser Zeichen abschneidet. Ein Wert Hallo; Welt kommt beim Lesen als Hallo zurück (#1427). Behandelt wird nur der Zeilenumbruch (\n), dazu \t und \\.

Wer Werte mit Semikolon, Rautezeichen oder führenden Leerzeichen speichern muss, kodiert sie selbst — etwa mit std.base64.

Konstanten

Konstante Wert Wirkung
MAX_SECTIONS 64 keine
MAX_ENTRIES_PER_SECTION 128 keine
MAX_KEY_LENGTH 64 keine
MAX_VALUE_LENGTH 256 keine
MAX_LINE_LENGTH 512 keine
INI_BUFFER_SIZE 65536 Größe des Zwischenpuffers beim Ändern
 
Die fünf Grenzwerte werden nirgends geprüft (#1429). Sie stammen aus einer früheren, tabellenbasierten Fassung; seit das Dokument roher Text ist, gibt es weder eine Sektionstabelle noch feste Feldlängen. Ein Schlüssel mit 70 Zeichen wird anstandslos gesetzt und gefunden, mehr als 64 Sektionen ebenso.

Die einzige wirksame Grenze ist INI_BUFFER_SIZE: Beim Ändern baut die Unit das Ergebnis in einem 64-KB-Puffer auf. Ein Dokument, das darüber hinauswächst, läuft über — ebenfalls ungeprüft.

Kommentare hinzufügen

 
AddSectionComment und AddKeyComment sind nicht umgesetzt und brechen ab. Beide melden seit lyxc 1.0.15G std.ini: AddSectionComment ist nicht umgesetzt (#1244) bzw. … AddKeyComment … und beenden das Programm. Vorher liefen sie still durch, ohne einen Kommentar zu schreiben — der Aufruf sah wie ein Erfolg aus.

Kommentare gehören also beim Zusammensetzen der Datei selbst gesetzt (BuildSectionHeader + eigene ; …-Zeilen). Beim Lesen und beim Roundtrip bleiben vorhandene Kommentare dagegen erhalten.

Fallstricke

  • Nur lesen ist sicher. Jedes Set…, das den Text verlängert, überschreibt fremden Speicher (#1426).
  • Jede Abfrage kostet Speicher (#1428) und durchsucht den gesamten Text linear — bei großen Dateien in Schleifen spürbar.
  • ; und # im Wert beenden ihn — auch ohne Leerzeichen davor.
  • Schlüssel- und Sektionsnamen werden nicht auf Gültigkeit geprüft, wenn man sie über SetString setzt; IsValidKeyName muss man selbst aufrufen.
  • Groß- und Kleinschreibung zählt bei Sektions- und Schlüsselnamen.
  • Kein Handle-Abbau. Weder ParseString noch LoadFile haben ein Gegenstück; der Dokumentpuffer bleibt bis zum Programmende belegt.
  • Doppelte Schlüssel: Gefunden wird der erste Treffer in der Sektion; SetString ersetzt ebenfalls nur diesen.

Weiterführend: std.json · std.yaml · std.env · Standard Library

Quelle: std/ini.lyx (1069 Zeilen) · Autor: Andreas Röne · Copyright: 2024–2025 Andreas Röne

Letzte Aktualisierung: 2026-08-13 — Seite gegen std/ini.lyx überarbeitet: zwei lauffähige Beispiele mit echter Ausgabe, das textbasierte Dokumentmodell und das Ausgabeformat von GetSections/GetKeys (dicht gepackt, nullterminiert) erstmals beschrieben, Fallstricke ergänzt. Belegt und als Issue erfasst: Dokumentpuffer wächst nicht mit (#1426), EscapeValue maskiert ;/# nicht (#1427), GetString leckt je Aufruf (#1428), die Grenzkonstanten sind wirkungslos (#1429). Geprüft mit lyxc 1.0.21A.