====== 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.
→ [[lyx_-_programmiersprache:units:lfd_factory|std.lfd_factory]] · [[lyx_-_programmiersprache:units|Standard Library]] · [[lyx_-_programmiersprache:units:alloc|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 Grammatikdatei ''lfd-ebnf.md'' beschreibt eine andere Sprache als der Parser.** Dort steht ''%%Form Anmeldung "Login" { Layout Vertical { … } }%%'' — großgeschrieben und mit Bezeichner zwischen Typ und Titel. Genau das weist der Parser mit ''-1'' und der Meldung ''expected '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 steht ''text = 0'' — der Parser legt für jede Eigenschaft einen Knoten mit dem **Schlüsselnamen** als Text an, speichert aber nur Zahlen und Wahrheitswerte (in ''numVal''). Der Wert von ''%%text: "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:
* **''-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.
* **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 ''LFDGetErrorCount'' abfragen. Bei mehr als 16 Fehlern in einem Block bricht der Parser die Schleife ab.
> **Ein Minuszeichen wird stillschweigend verschluckt:** ''%%width: -5%%'' ergibt 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 [[lyx_-_programmiersprache:units:lfd_factory|std.lfd_factory]] — die ist derzeit eine leere Hülle, weshalb dieser Weg der einzige ist.
----
===== Speicher und Lebensdauer =====
* ''LFDParseString'' belegt zwei Puffer über [[lyx_-_programmiersprache:units:alloc|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.
* **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.
* ''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.
* 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; ''Button'' ist ein Bezeichner, kein Bedienelement.
* ''-1'' vor jeder Weiterverarbeitung prüfen, danach ''LFDGetErrorCount''.
* 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 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.
* Kein Kommentarzeichen: alles, was nicht Schlüsselwort, Zeichenkette, Zahl oder Satzzeichen ist, wird als Bezeichner gelesen.
----
**Weiterführend:** [[lyx_-_programmiersprache:units:lfd_factory|std.lfd_factory — vorgesehene Widget-Factory]] · [[lyx_-_programmiersprache:units:alloc|std.alloc]] · [[lyx_-_programmiersprache:units|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''.