====== 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''.