====== 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.
→ [[lyx_-_programmiersprache:units:string|std.string]] · [[lyx_-_programmiersprache:units:fs|std.fs]] · [[lyx_-_programmiersprache:units|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 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1426|#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 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1428|#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 [[https://github.com/SEOLizer/LyX-Compiler/issues/1426|#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 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1427|#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 [[lyx_-_programmiersprache:units:base64|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** ([[https://github.com/SEOLizer/LyX-Compiler/issues/1429|#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 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1426|#1426]]).
* **Jede Abfrage kostet Speicher** ([[https://github.com/SEOLizer/LyX-Compiler/issues/1428|#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:** [[lyx_-_programmiersprache:units:json|std.json]] · [[lyx_-_programmiersprache:units:yaml|std.yaml]] · [[lyx_-_programmiersprache:units:env|std.env]] · [[lyx_-_programmiersprache:units|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 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1426|#1426]]), ''EscapeValue'' maskiert '';''/''#'' nicht ([[https://github.com/SEOLizer/LyX-Compiler/issues/1427|#1427]]), ''GetString'' leckt je Aufruf ([[https://github.com/SEOLizer/LyX-Compiler/issues/1428|#1428]]), die Grenzkonstanten sind wirkungslos ([[https://github.com/SEOLizer/LyX-Compiler/issues/1429|#1429]]). Geprüft mit ''lyxc 1.0.21A''.