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.
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).
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.
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 |
| 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). |
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.
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:
-1 ist kein Knoten. Wird der Rückgabewert ungeprüft an LFDGetNodeChildCount oder LFDGetNodeType weitergereicht, liest die Funktion von Adresse -1 — das Programm stürzt ab. Immer zuerst auf < 0 prüfen.LFDGetErrorCount abfragen. 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.
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.
LFDParseString belegt zwei Puffer über std.alloc: Token (Quelllänge + 4 Einträge à 24 Byte) und Knoten (Quelllänge / 4 + 16 Einträge à 48 Byte). Für 1 KB Quelltext sind das rund 37 KB.LFDGetNodeText zeigt 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.LFDParserFree gibt beides frei. Danach darf kein Knoten mehr benutzt werden.Button ist ein Bezeichner, kein Bedienelement.-1 vor jeder Weiterverarbeitung prüfen, danach LFDGetErrorCount.text, tooltip, onclick) sind über die Schnittstelle nicht auslesbar — nur der Elementtext hinter dem Schlüsselwort ist es.LFD_NODE_WIDGET (3) kollidiert mit LFD_TK_HORIZONTAL (3) — für „ist das ein Bedienelement?“ den Bereich LFD_TK_BUTTON…LFD_TK_CUSTOM prüfen.LFDValidate prüft nur, ob überhaupt Text vorliegt — es ersetzt kein Parsen.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.