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