std.lfd_parser
Parser für LFD (LyX Form Description) — eine kleine Textsprache, mit der sich Bedienoberflächen beschreiben lassen. Die Unit zerlegt den Quelltext in Token und baut daraus einen begehbaren Syntaxbaum; was daraus wird — Qt-Fenster, Terminaloberfläche, HTML — entscheidet der Aufrufer.
→ std.lfd_factory · Standard Library · std.alloc
Alle Beispiele dieser Seite sind mit lyxc 1.0.21A übersetzt und ausgeführt; die gezeigten Ausgaben sind echte Programmausgaben.
Kein Binärformat. Frühere Fassungen dieser Seite beschrieben LFD als „typisiertes, kompaktes Binärformat — ähnlich MessagePack„. Das trifft nichts davon: LFD ist Klartext, und die Unit ist ein Recursive-Descent-Parser dafür. Auch das@description-Attribut der Unit („LFD binary file format parser“) sagt das Falsche.
Die Sprache
Ein Formular ist ein form-Block; darin stehen Anordnungen, Bedienelemente und deren Eigenschaften. Alle Schlüsselwörter sind klein geschrieben.
form {
vertical {
label "Benutzer" { }
input "" { width: 200 }
button "Anmelden" { width: 100 height: 30 enabled: true }
}
}
Aufbau eines Elements: Schlüsselwort, optional ein Text in Anführungszeichen, dann ein Block in geschweiften Klammern. Eigenschaften sind name: wert-Paare.
| Kategorie | Schlüsselwörter |
|---|---|
| Wurzel | form |
| Anordnungen | layout, vertical, horizontal, grid, stack |
| Bedienelemente | button, label, input, checkbox, radiobutton, combobox, spinbox, slider, listbox, textedit, progressbar, groupbox, tabwidget, splitter, image, custom |
| Eigenschaften | text, tooltip, enabled, visible, width, height |
| Ereignisse | onclick, onchange |
| Werte | Zeichenkette, Zahl, true, false |
Ein Name, der in keiner dieser Listen steht, gilt als Bezeichner und ist als Eigenschaftsname zulässig — farbe: 7 wird also angenommen.
Die Grammatikdateilfd-ebnf.mdbeschreibt eine andere Sprache als der Parser. Dort stehtForm Anmeldung "Login" { Layout Vertical { … } }— großgeschrieben und mit Bezeichner zwischen Typ und Titel. Genau das weist der Parser mit-1und der Meldungexpected 'form' ab. Maßgeblich ist die Implementierung; die EBNF ist ein Entwurf (Status „Draft„, v0.1.0).
Parsen und den Baum lesen
import std.lfd_parser;
import std.string;
// Knotentext: Zeiger + Laenge stehen im Knoten (Offsets 8/16), einen Getter
// fuer die Laenge gibt es nicht - deshalb peek64(node + 16).
fn Text(node: int64): pchar {
var p: int64 := LFDGetNodeText(node) as int64;
var l: int64 := peek64(node + 16);
var b: int64 := alloc(l + 1);
var i: int64 := 0;
while (i < l) { poke8(b + i, peek8(p + i)); i := i + 1; }
poke8(b + l, 0);
return b as pchar;
}
fn main(): int64 {
var root: int64 := LFDParseString("form { button \"Speichern\" { width: 100 enabled: true text: \"OK\" } }"c);
PrintLn(StrConcat("root type (0=form): ", IntToStr(LFDGetNodeType(root))));
var btn: int64 := LFDGetNodeChild(root, 0);
PrintLn(StrConcat("btn type (6=button): ", IntToStr(LFDGetNodeType(btn))));
PrintLn(StrConcat("btn text: ", Text(btn)));
PrintLn(StrConcat("btn Eigenschaften: ", IntToStr(LFDGetNodeChildCount(btn))));
var i: int64 := 0;
while (i < LFDGetNodeChildCount(btn)) {
var p: int64 := LFDGetNodeChild(btn, i);
PrintLn(StrConcat(StrConcat(" ", Text(p)), StrConcat(" = ", IntToStr(LFDGetNodeIntVal(p)))));
i := i + 1;
}
PrintLn(StrConcat("Fehler: ", IntToStr(LFDGetErrorCount(0))));
return 0;
}
root type (0=form): 0
btn type (6=button): 6
btn text: Speichern
btn Eigenschaften: 3
width = 100
enabled = 1
text = 0
Fehler: 0
Zeichenketten-Werte gehen verloren. In der Ausgabe stehttext = 0— der Parser legt für jede Eigenschaft einen Knoten mit dem Schlüsselnamen als Text an, speichert aber nur Zahlen und Wahrheitswerte (innumVal). Der Wert vontext: "OK"wird gelesen und verworfen; er ist über die öffentliche Schnittstelle nicht mehr erreichbar. Wer Texteigenschaften braucht, muss den Quelltext zusätzlich selbst auswerten oder den Parser erweitern.
Knotenmodell
Jeder Knoten ist 48 Byte groß (6 × int64):
| Offset | Feld | Zugriff |
|---|---|---|
| +0 | Knotenart (LFD_TK_* bzw. LFD_NODE_*) | LFDGetNodeType |
| +8 | Byte-Versatz des Textes in der Quelle | LFDGetNodeText (liefert den Zeiger) |
| +16 | Textlänge | kein Getter — peek64(node + 16) |
| +24 | erstes Kind | LFDGetNodeChild(node, 0) |
| +32 | nächstes Geschwister | über LFDGetNodeChild(parent, i) |
| +40 | Zahlwert der Eigenschaft | LFDGetNodeIntVal |
Die Knotenarten sind dieselben Zahlen wie die Token-Arten: LFD_TK_FORM = 0, LFD_TK_VERTICAL = 2, LFD_TK_HORIZONTAL = 3, LFD_TK_GRID = 4, LFD_TK_STACK = 5, dann die Bedienelemente LFD_TK_BUTTON = 6 bis LFD_TK_CUSTOM = 21. Eigenschaftsknoten tragen LFD_NODE_PROPERTY = 101.
Praktisch für eigene Durchläufe: alle Bedienelemente liegen im Bereich LFD_TK_BUTTON…LFD_TK_CUSTOM, alle Anordnungen zwischen LFD_TK_LAYOUT und LFD_TK_STACK.
| Alias | Wert | Anmerkung |
|---|---|---|
LFD_NODE_FORM | 0 | identisch mit LFD_TK_FORM |
LFD_NODE_LAYOUT | 1 | identisch mit LFD_TK_LAYOUT |
LFD_NODE_WIDGET | 3 | Vorsicht: derselbe Wert wie LFD_TK_HORIZONTAL — als Vergleichswert unbrauchbar |
LFD_NODE_ROOT | 100 | wird vom Parser nie vergeben |
LFD_NODE_PROPERTY | 101 | Eigenschaftsknoten |
LFD_NODE_EVENT | 102 | wird vom Parser nie vergeben; onclick erzeugt einen gewöhnlichen Eigenschaftsknoten |
Funktionen
Heutige Schnittstelle
| Signatur | Beschreibung |
|---|---|
LFDParseString(input: pchar): int64 | Parst den Quelltext und liefert den Wurzelknoten oder -1. Legt Token- und Knotenpuffer neu an; alle Knoten aus einem früheren Aufruf werden damit ungültig. |
LFDGetNodeType(node: int64): int64 | Knotenart, -1 bei node == 0. |
LFDGetNodeChild(node: int64, index: int64): int64 | index-tes Kind oder 0. |
LFDGetNodeChildCount(node: int64): int64 | Zahl der direkten Kinder. |
LFDGetNodeText(node: int64): pchar | Zeiger auf den Text im Quelltext — nicht nullterminiert. Länge über peek64(node + 16). |
LFDGetNodeIntVal(node: int64): int64 | Zahl- oder Wahrheitswert einer Eigenschaft (true = 1, false = 0). |
Ältere Schnittstelle
Sie stammt aus der Zeit vor LFDParseString und arbeitet auf globalem Zustand; der parser-Parameter wird nirgends ausgewertet — jeder Wert ist zulässig, üblich ist 0.
| Signatur | Beschreibung |
|---|---|
LFDParserNew(sourceText: pchar): int64 | Merkt den Quelltext, setzt den Fehlerzähler zurück, liefert immer 1. Parst nicht. |
LFDValidate(parser: int64): int64 | 1, wenn ein nicht-leerer Quelltext gemerkt ist, sonst 0. Prüft die Syntax nicht. |
LFDGetLastError(parser: int64): pchar | Letzte Fehlermeldung, sonst leerer Text. |
LFDGetErrorCount(parser: int64): int64 | Zahl der Fehler des letzten Laufs. |
LFDParserFree(parser: int64): void | Gibt Token- und Knotenpuffer frei und setzt den Zustand zurück. |
LFDGetLastError und LFDGetErrorCount gehören zwar formal zur älteren Schnittstelle, sind aber auch nach LFDParseString die einzige Möglichkeit, an Fehlerdetails zu kommen.
Fehlerverhalten
import std.lfd_parser;
import std.string;
fn Probe(name: pchar, quelle: pchar): void {
var r: int64 := LFDParseString(quelle);
Print(StrConcat(name, ": "));
if (r < 0) {
// Wichtig: bei -1 darf der Wert NICHT an LFDGetNode* weitergereicht werden
PrintLn(StrConcat(StrConcat("abgewiesen (-1), Fehler ", IntToStr(LFDGetErrorCount(0))),
StrConcat(", Meldung: ", LFDGetLastError(0))));
return;
}
Print(StrConcat("Kinder ", IntToStr(LFDGetNodeChildCount(r))));
Print(StrConcat(" Fehler ", IntToStr(LFDGetErrorCount(0))));
PrintLn(StrConcat(" Meldung: ", LFDGetLastError(0)));
}
fn main(): int64 {
Probe("klein geschrieben ", "form { button \"OK\" { } }"c);
Probe("Gross geschrieben ", "Form { Button \"OK\" { } }"c);
Probe("ohne form ", "invalid { }"c);
Probe("leerer Text ", ""c);
Probe("fehlende Klammer ", "form { button \"OK\" { "c);
Probe("negative Zahl ", "form { button \"A\" { width: -5 } }"c);
Probe("unbekannte Eigensch", "form { button \"A\" { farbe: 7 } }"c);
Probe("Eigenschaft ohne : ", "form { button \"A\" { width 7 } }"c);
return 0;
}
klein geschrieben : Kinder 1 Fehler 0 Meldung:
Gross geschrieben : abgewiesen (-1), Fehler 1, Meldung: expected 'form'
ohne form : abgewiesen (-1), Fehler 1, Meldung: expected 'form'
leerer Text : abgewiesen (-1), Fehler 1, Meldung: empty input
fehlende Klammer : Kinder 1 Fehler 2 Meldung: unexpected token
negative Zahl : Kinder 1 Fehler 0 Meldung:
unbekannte Eigensch: Kinder 1 Fehler 0 Meldung:
Eigenschaft ohne : : Kinder 1 Fehler 1 Meldung: expected ':'
Zwei Dinge stehen hier drin, die man leicht übersieht:
-1ist kein Knoten. Wird der Rückgabewert ungeprüft anLFDGetNodeChildCountoderLFDGetNodeTypeweitergereicht, liest die Funktion von Adresse-1— das Programm stürzt ab. Immer zuerst auf< 0prüfen.- Ein gültiger Wurzelknoten heißt nicht, dass alles gelesen wurde. Bei fehlender schließender Klammer liefert der Parser einen Baum und zwei Fehler. Nach jedem Parse also zusätzlich
LFDGetErrorCountabfragen. Bei mehr als 16 Fehlern in einem Block bricht der Parser die Schleife ab.
Ein Minuszeichen wird stillschweigend verschluckt:width: -5ergibt den Wert 5, ohne Fehlermeldung. Die Zahlenauswertung liest nur Ziffern. Negative Werte lassen sich in LFD derzeit nicht ausdrücken.
Beispiel: Formular auswerten
Der Baum lässt sich rekursiv durchlaufen wie jeder andere auch — hier eine Auswertung, die Bedienelemente zählt und Breiten summiert:
import std.lfd_parser;
import std.string;
var anzahlWidgets: int64 := 0;
var anzahlProps: int64 := 0;
var summeBreite: int64 := 0;
fn IstWidget(k: int64): int64 {
if (k >= LFD_TK_BUTTON && k <= LFD_TK_CUSTOM) { return 1; }
return 0;
}
fn Text(node: int64): pchar {
var p: int64 := LFDGetNodeText(node) as int64;
var l: int64 := peek64(node + 16);
var b: int64 := alloc(l + 1);
var i: int64 := 0;
while (i < l) { poke8(b + i, peek8(p + i)); i := i + 1; }
poke8(b + l, 0);
return b as pchar;
}
fn Durchlauf(node: int64): void {
var k: int64 := LFDGetNodeType(node);
if (IstWidget(k) == 1) { anzahlWidgets := anzahlWidgets + 1; }
if (k == LFD_NODE_PROPERTY) {
anzahlProps := anzahlProps + 1;
if (StrEquals(Text(node), "width") == 1) {
summeBreite := summeBreite + LFDGetNodeIntVal(node);
}
}
var n: int64 := LFDGetNodeChildCount(node);
var i: int64 := 0;
while (i < n) { Durchlauf(LFDGetNodeChild(node, i)); i := i + 1; }
}
fn main(): int64 {
var quelle: pchar := "form { horizontal { button \"Ja\" { width: 80 height: 24 } button \"Nein\" { width: 80 } checkbox \"Merken\" { width: 120 enabled: false } } }"c;
var root: int64 := LFDParseString(quelle);
if (root < 0) { PrintLn("Parse abgewiesen"); return 1; }
if (LFDGetErrorCount(0) > 0) {
PrintLn(StrConcat("Baum unvollstaendig: ", LFDGetLastError(0)));
return 1;
}
Durchlauf(root);
PrintLn(StrConcat("Bedienelemente: ", IntToStr(anzahlWidgets)));
PrintLn(StrConcat("Eigenschaften: ", IntToStr(anzahlProps)));
PrintLn(StrConcat("Breite insgesamt: ", IntToStr(summeBreite)));
LFDParserFree(0);
return 0;
}
Bedienelemente: 3
Eigenschaften: 5
Breite insgesamt: 280
Ein Durchlauf, der statt zu zählen Widgets erzeugt, wäre die Aufgabe von std.lfd_factory — die ist derzeit eine leere Hülle, weshalb dieser Weg der einzige ist.
Speicher und Lebensdauer
LFDParseStringbelegt zwei Puffer über std.alloc: Token (Quelllänge + 4Einträge à 24 Byte) und Knoten (Quelllänge / 4 + 16Einträge à 48 Byte). Für 1 KB Quelltext sind das rund 37 KB.- Jeder neue Parse gibt die alten Puffer frei. Knotenzeiger aus einem früheren Aufruf sind danach ungültig — sie zeigen womöglich auf wiederverwendeten Speicher und liefern noch plausibel aussehende Werte. Alte Zeiger nicht aufheben.
LFDGetNodeTextzeigt in den Quelltext, nicht in einen eigenen Puffer. Der Quelltext muss also mindestens so lange leben wie der Baum; ein Text aus einem freigegebenen Puffer macht alle Knotentexte unbrauchbar.LFDParserFreegibt beides frei. Danach darf kein Knoten mehr benutzt werden.- Der Parser hält seinen Zustand global: es gibt immer nur einen Baum gleichzeitig. Zwei Formulare nebeneinander im Speicher gehen nicht — erst auswerten, dann das nächste parsen.
Fallstricke
- Schlüsselwörter sind klein zu schreiben;
Buttonist ein Bezeichner, kein Bedienelement. -1vor jeder Weiterverarbeitung prüfen, danachLFDGetErrorCount.- Zeichenketten-Eigenschaften (
text,tooltip,onclick) sind über die Schnittstelle nicht auslesbar — nur der Elementtext hinter dem Schlüsselwort ist es. - Negative Zahlen werden zu ihrem Betrag.
LFD_NODE_WIDGET(3) kollidiert mitLFD_TK_HORIZONTAL(3) — für „ist das ein Bedienelement?“ den BereichLFD_TK_BUTTON…LFD_TK_CUSTOMprüfen.LFDValidateprüft nur, ob überhaupt Text vorliegt — es ersetzt kein Parsen.- Kein Kommentarzeichen: alles, was nicht Schlüsselwort, Zeichenkette, Zahl oder Satzzeichen ist, wird als Bezeichner gelesen.
Weiterführend: std.lfd_factory — vorgesehene Widget-Factory · std.alloc · Standard Library
Quelle: std/lfd_parser.lyx · Autor: Andreas Röne · Copyright: 2024–2025 Andreas Röne
Letzte Aktualisierung: 2026-08-13 — Seite gegen std/lfd_parser.lyx neu geschrieben. Die alte Fassung beschrieb LFD als Binärformat „ähnlich MessagePack„ — es ist eine Textsprache für Formularbeschreibungen. Ergänzt: Sprachbeschreibung mit vollständiger Schlüsselwortliste, Knotenmodell samt Offsets, drei lauffähige Beispiele mit echter Ausgabe, das Fehlerverhalten in acht Fällen und die Lebensdauerregeln. Belegt und dokumentiert: Zeichenketten-Werte gehen verloren, -1 führt bei ungeprüfter Weitergabe zum Absturz, ein gültiger Baum kann trotzdem Fehler tragen, -5 wird zu 5, LFD_NODE_WIDGET kollidiert mit LFD_TK_HORIZONTAL. Geprüft mit lyxc 1.0.21A.
