====== Lyx Compiler — KI-Integration (WP-KI-01 / WP-KI-02) ====== Zwei neue Compiler-Flags ermöglichen maschinenlesbare JSON-Ausgabe für KI-Agenten, Language-Server, IDEs und Build-Tooling: ''%%--%%ast-json'' exportiert den vollständigen Parser-AST, ''%%--%%error-json'' liefert strukturierte Fehler-Records statt Freitext. → [[lyx_-_programmiersprache:tools|Tools & Compiler]] · [[lyx_-_programmiersprache:tools:compiler-parameter|Compiler-Parameter]] ---- ===== Flags im Überblick ===== ^ Flag ^ Zweck ^ | ''%%--%%ast-json'' | Parser-AST als JSON-Array auf stdout ausgeben, dann beenden | | ''%%--%%ast-json-pretty'' | Wie ''%%--%%ast-json'', zusätzlich 2-Leerzeichen-Einrückung | | ''%%--%%error-json'' | Compiler-Fehler (Sema) als strukturiertes JSON statt Freitext; Exit-Code 1 bei Fehler | **Reihenfolge im Compile-Ablauf:** * ''%%--%%ast-json'' / ''%%--%%ast-json-pretty'': endet **nach dem Parsen**, vor der Semantic Analysis. Sema und Codegen laufen nicht. * ''%%--%%error-json'': läuft durch alle Passes; bei Sema-Fehler wird JSON ausgegeben und mit Exit 1 beendet. Bei Erfolg kein JSON-Output. ---- ===== --ast-json / --ast-json-pretty ===== Implementiert in ''src/backend/ast_json.lyx'' (Klasse ''AstJson''). ==== Schnellstart ==== # Kompakter AST eines Lyx-Programms lyxc main.lyx --ast-json > ast.json # Lesbar formatiert (2-Leerzeichen-Einrückung) lyxc main.lyx --ast-json-pretty > ast.json # AST pipen und mit jq auswerten lyxc main.lyx --ast-json | jq '.[].kind' lyxc main.lyx --ast-json | jq '[.[] | select(.kind=="NK_FUNC_DECL") | .sval]' ==== JSON-Struktur ==== Die Ausgabe ist ein JSON-Array der Top-Level-Knoten (Deklarationen). Jeder Knoten ist ein Objekt mit folgenden Feldern: ^ Feld ^ Typ ^ Pflicht ^ Bedeutung ^ | ''kind'' | string | ✔ | Symbolischer Knotentyp (z.B. ''NK_FUNC_DECL'') | | ''kindId'' | integer | ✔ | Numerische Knoten-ID | | ''line'' | integer | ✔ | Quellzeile (1-basiert; 0 = unbekannt) | | ''col'' | integer | ✔ | Spalte ab Zeilenbeginn (0-basiert; 0 = unbekannt) | | ''sval'' | string | nur wenn vorhanden | String-Wert (Bezeichner, String-Literal, Typname …) | | ''ival'' | integer | nur wenn ≠ 0 | Integer-Wert (Literal, Operatorkode …) | | ''pub'' | true | nur wenn vorhanden | Knoten ist mit ''pub'' deklariert | | ''c0'' | array oder null | ✔ | Kinder-Sibling-Kette (Slot 0) | | ''c1'' | array oder null | ✔ | Kinder-Sibling-Kette (Slot 1) | | ''c2'' | array oder null | ✔ | Kinder-Sibling-Kette (Slot 2) | | ''c3'' | array oder null | ✔ | Kinder-Sibling-Kette (Slot 3) — Sonderfall: siehe unten | | ''libSpan'' | integer | Sonderfall | Nur bei ''NK_FUNC_DECL'' mit ''c2 < 0'' (extern fn): Library-Span statt ''c3''-Array | **Sonderfall extern fn:** Wenn ein ''NK_FUNC_DECL''-Knoten keinen Body hat (extern-Deklaration), ist ''c2 = -1'' und das Feld ''c3'' entfällt. Stattdessen erscheint ''libSpan'' mit dem Integer-Wert des Library-Identifiers. ==== Beispiel-Ausgabe ==== Für ''pub fn Add(a: int64, b: int64): int64 { return a + b; }'': [ { "kind": "NK_FUNC_DECL", "kindId": 2, "line": 1, "col": 7, "sval": "Add", "pub": true, "c0": [ {"kind": "NK_PARAM", "kindId": 3, "line": 1, "col": 11, "sval": "a", "c0": [{"kind": "NK_TYPE_NAME", "kindId": 60, "line": 1, "col": 14, "sval": "int64", "c0": null, "c1": null, "c2": null, "c3": null}], "c1": null, "c2": null, "c3": null}, {"kind": "NK_PARAM", "kindId": 3, "line": 1, "col": 23, "sval": "b", "c0": [{"kind": "NK_TYPE_NAME", "kindId": 60, "line": 1, "col": 26, "sval": "int64", "c0": null, "c1": null, "c2": null, "c3": null}], "c1": null, "c2": null, "c3": null} ], "c1": [{"kind": "NK_TYPE_NAME", "kindId": 60, "line": 1, "col": 40, "sval": "int64", "c0": null, "c1": null, "c2": null, "c3": null}], "c2": null, "c3": [ {"kind": "NK_BLOCK", "kindId": 20, "line": 1, "col": 47, "c0": [{"kind": "NK_RETURN", "kindId": 23, ...}], "c1": null, "c2": null, "c3": null} ] } ] ==== NK_*-Knotentypen ==== Vollständige Tabelle aller 116 Parser-Knotentypen: **Deklarationen** ^ ID ^ Name ^ Bedeutung ^ | 0 | ''NK_INVALID'' | Ungültiger Knoten (Initialisierungswert) | | 1 | ''NK_IMPORT'' | ''import''-Anweisung | | 2 | ''NK_FUNC_DECL'' | Funktionsdeklaration (''pub fn'', ''fn'', extern fn) | | 3 | ''NK_PARAM'' | Funktionsparameter | | 4 | ''NK_VAR_DECL'' | ''var''-Deklaration | | 5 | ''NK_CON_DECL'' | ''con''-Konstante | | 6 | ''NK_STRUCT_DECL'' | ''struct''-Deklaration | | 7 | ''NK_FIELD_DECL'' | Strukturfeld | | 8 | ''NK_ENUM_DECL'' | ''enum''-Deklaration | | 9 | ''NK_ENUM_MEM'' | Enum-Member | | 10 | ''NK_TYPE_DECL'' | ''type''-Alias-Deklaration | | 63 | ''NK_GENERIC_DECL'' | Generische Typ-Deklaration | | 69 | ''NK_CLASS_DECL'' | ''class''-Deklaration | | 70 | ''NK_IFACE_DECL'' | ''interface''-Deklaration | | 90 | ''NK_DIM_DECL'' | ''dim''-Deklaration | | 91 | ''NK_UTYPE_DECL'' | Unit-Typ-Deklaration | | 110 | ''NK_CAPABILITY_ATTR'' | ''@capability''-Attribut | | 111 | ''NK_CAPABILITY_DECL'' | Capability-Deklaration | | 112 | ''NK_CAPABILITY_ARG'' | Capability-Argument | | 113 | ''NK_NETWORK_TARGET'' | Netzwerk-Target-Deklaration | | 114 | ''NK_GRANT_CLAUSE'' | ''grant''-Klausel | | 115 | ''NK_RESTRICT_CLAUSE'' | ''restrict''-Klausel | | 116 | ''NK_USES_CALLER_CAP'' | ''uses_caller_cap''-Annotation | **Statements** ^ ID ^ Name ^ Bedeutung ^ | 20 | ''NK_BLOCK'' | Block ''{ … }'' | | 21 | ''NK_IF'' | ''if''-Statement | | 22 | ''NK_WHILE'' | ''while''-Schleife | | 23 | ''NK_RETURN'' | ''return''-Statement | | 24 | ''NK_EXPR_STMT'' | Ausdruck als Statement | | 25 | ''NK_ASSIGN'' | Zuweisung '':='' | | 26 | ''NK_FOR'' | ''for''-Schleife (foreach) | | 27 | ''NK_SWITCH'' | ''switch''-Statement | | 28 | ''NK_CASE'' | ''case''-Zweig | | 29 | ''NK_BREAK'' | ''break'' | | 30 | ''NK_DISPOSE'' | ''dispose''-Statement | | 31 | ''NK_TRY'' | ''try''-Block | | 32 | ''NK_CATCH'' | ''catch''-Block | | 33 | ''NK_FINALLY'' | ''finally''-Block | | 34 | ''NK_THROW'' | ''throw''-Statement | | 35 | ''NK_CONTINUE'' | ''continue'' | | 36 | ''NK_ASSERT'' | ''assert''-Statement | | 77 | ''NK_REPEAT_UNTIL'' | ''repeat … until''-Schleife | | 78 | ''NK_TUPLE_VAR_DECL'' | Tuple-Destrukturierung | | 102 | ''NK_FOR_C'' | C-Stil-''for''-Schleife | | 106 | ''NK_DEFER'' | ''defer''-Statement | | 107 | ''NK_FOR_RANGE'' | ''for .. in range''-Schleife | **Ausdrücke** ^ ID ^ Name ^ Bedeutung ^ | 40 | ''NK_LIT_INT'' | Integer-Literal | | 41 | ''NK_LIT_FLOAT'' | Float-Literal | | 42 | ''NK_LIT_STR'' | String-Literal | | 43 | ''NK_LIT_CHAR'' | Char-Literal | | 44 | ''NK_LIT_BOOL'' | ''true'' / ''false'' | | 45 | ''NK_LIT_NULL'' | ''null'' | | 46 | ''NK_IDENT'' | Bezeichner | | 47 | ''NK_BINOP'' | Binärer Operator; ''ival'' enthält Operator-Code | | 48 | ''NK_UNOP'' | Unärer Operator | | 49 | ''NK_CALL'' | Funktionsaufruf | | 50 | ''NK_INDEX'' | Array-Index ''a[i]'' | | 51 | ''NK_FIELD'' | Feldzugriff ''a.b'' | | 52 | ''NK_NEW'' | ''new''-Ausdruck | | 53 | ''NK_CAST'' | ''as''-Cast | | 54 | ''NK_CLOSURE'' | Closure / Lambda | | 55 | ''NK_FN_PTR'' | Funktionszeiger | | 71 | ''NK_SUPER_CALL'' | ''super.method()''-Aufruf | | 72 | ''NK_IS_EXPR'' | ''is''-Typprüfung | | 73 | ''NK_RANGE'' | Bereichsausdruck ''a..b'' | | 79 | ''NK_MAP_LIT'' | Map-Literal | | 80 | ''NK_SET_LIT'' | Mengen-Literal | | 81 | ''NK_IN_EXPR'' | ''in''-Ausdruck | | 82 | ''NK_MAP_ENTRY'' | Map-Eintrag (Schlüssel:Wert) | | 83 | ''NK_POOL'' | Memory-Pool-Ausdruck | | 84 | ''NK_PANIC'' | ''panic''-Ausdruck | | 86 | ''NK_FORMAT_EXPR'' | Formatierter String-Ausdruck | | 87 | ''NK_NULL_COAL'' | Null-Coalescing ''??'' | | 88 | ''NK_SAFE_CAST'' | Sicherer Cast mit Fallback | | 89 | ''NK_REGEX_LIT'' | Regulärer-Ausdruck-Literal | | 94 | ''NK_SIMD_NEW'' | SIMD-Array-Allokation | | 100 | ''NK_ARRAY_LIT'' | Array-Literal | | 108 | ''NK_NAMED_ARG'' | Benanntes Argument ''key: val'' | | 109 | ''NK_TYPE_TUPLE'' | Tuple-Typ | **Typen** ^ ID ^ Name ^ Bedeutung ^ | 60 | ''NK_TYPE_NAME'' | Einfacher Typname (''int64'', ''bool'', benutzerdefinierter Typ) | | 61 | ''NK_TYPE_ARRAY'' | Array-Typ ''array'' | | 62 | ''NK_TYPE_PARAM'' | Typ-Parameter (Generics) | | 92 | ''NK_TYPE_RINGBUF'' | Ringpuffer-Typ | | 93 | ''NK_TYPE_PARALLEL'' | Parallel-Typ | | 101 | ''NK_TYPE_ARRAY_FIXED'' | Fest dimensioniertes Array ''[N]T'' | **Pattern-Matching** ^ ID ^ Name ^ Bedeutung ^ | 64 | ''NK_MATCH'' | ''match''-Ausdruck | | 65 | ''NK_MATCH_CASE'' | ''case''-Zweig im ''match'' | | 66 | ''NK_PATTERN_LIT'' | Literal-Pattern | | 67 | ''NK_PATTERN_WILD'' | Wildcard-Pattern ''_'' | | 68 | ''NK_PATTERN_BIND'' | Bind-Pattern (Variable binden) | | 74 | ''NK_WHERE'' | ''where''-Klausel | | 75 | ''NK_PATTERN_ENUM'' | Enum-Pattern | | 76 | ''NK_PATTERN_STRUCT'' | Struct-Destrukturierung | | 85 | ''NK_PATTERN_OR'' | Oder-Pattern ''A | B'' | **LFD (Qt-UI-Builder)** ^ ID ^ Name ^ Bedeutung ^ | 95 | ''NK_LFD_FORM'' | LFD-Form-Wurzel | | 96 | ''NK_LFD_WIDGET'' | Widget-Deklaration | | 97 | ''NK_LFD_LAYOUT'' | Layout-Deklaration | | 98 | ''NK_LFD_PROPERTY'' | Widget-Property | | 99 | ''NK_LFD_SIGNAL'' | Signal-Handler-Binding | **Compiler-Direktiven** ^ ID ^ Name ^ Bedeutung ^ | 103 | ''NK_AT_IF'' | ''@if''-Direktive | | 104 | ''NK_AT_DIRECTIVE'' | Allgemeine ''@''-Direktive | | 105 | ''NK_AT_ENERGY'' | ''@energy''-Attribut | ==== Internes Node-Record-Layout ==== Jeder Parser-Knoten belegt 88 Bytes im internen nodes-Buffer: ^ Offset ^ Bytes ^ Feld ^ Bedeutung ^ | +0 | 8 | ''kind'' | Knotentyp (NK_*-ID) | | +8 | 8 | ''c0'' | Index des ersten Knotens in Slot 0 (oder -1) | | +16 | 8 | ''c1'' | Index des ersten Knotens in Slot 1 (oder -1) | | +24 | 8 | ''c2'' | Index des ersten Knotens in Slot 2 (oder -1) | | +32 | 8 | ''c3'' | Index des ersten Knotens in Slot 3 (oder -1); bei extern fn: Library-Span | | +40 | 8 | ''next'' | Nächster Sibling in der Sibling-Kette (-1 = Ende) | | +48 | 8 | ''tok'' | Token-Index (Verweis in Token-Buffer) | | +56 | 8 | ''ival'' | Integer-Wert (Literal-Wert, Operator-Code o.ä.) | | +64 | 8 | ''soff'' | Offset des String-Werts im Quelltext | | +72 | 8 | ''slen'' | Länge des String-Werts (0 = kein String) | | +80 | 8 | ''pub'' | ≠ 0 wenn ''pub''-deklariert | Token-Records sind 40 Bytes; Zeile bei +32, Start-Position (für col) bei +8. ==== AstJson-Klasse (API) ==== Die Klasse ist in ''src/backend/ast_json.lyx'' als ''pub type AstJson'' definiert: ^ Funktion ^ Beschreibung ^ | ''AjInit(nodes, src, toks, tokCount, fd)'' | Initialisiert mit Parser-Daten; ''fd'': Ausgabe-FD (1 = stdout) | | ''AjDeinit()'' | Flusht Puffer und gibt Scratch-Speicher frei | | ''AjDump(root: int64)'' | Gibt den gesamten AST ab Knoten ''root'' als JSON-Array aus | | ''pretty: bool'' | Auf ''true'' setzen für 2-Leerzeichen-Einrückung (''%%--%%ast-json-pretty'') | Intern: Write-Batch-Buffer (4096 Bytes) reduziert sys_write-Aufrufe. Dezimal-Konvertierung über 24-Byte Scratch-Buffer. ---- ===== --error-json ===== Implementiert in ''src/error_collector.lyx'' (Klasse ''ErrorCollector'') und in ''src/sema.lyx'' (''errColl''-Integration). ==== Schnellstart ==== # Fehler als JSON ausgeben statt Freitext lyxc main.lyx --error-json # In CI: JSON in Datei speichern, Exit-Code auswerten lyxc main.lyx --error-json > errors.json echo "Exit: $?" # 0 = ok, 1 = Sema-Fehler # Mit jq: nur Fehler-Messages extrahieren lyxc main.lyx --error-json | jq '.errors[].message' ==== JSON-Struktur ==== { "errors": [ { "errorId": "LYX-S0001", "category": "sema", "severity": "error", "message": "Unbekannter Bezeichner 'foo'", "locations": [{"file": "main.lyx", "line": 42, "col": 0}], "expected": null, "actual": null, "suggestion": null, "astPath": null }, { "errorId": "LYX-W0001", "category": "sema", "severity": "warning", "message": "Variable 'x' wird nie gelesen", "locations": [{"file": "main.lyx", "line": 7, "col": 0}], "expected": null, "actual": null, "suggestion": null, "astPath": null } ], "errorCount": 1, "warningCount": 1 } **Toplevel-Felder:** ^ Feld ^ Typ ^ Bedeutung ^ | ''errors'' | array | Alle Einträge (Fehler + Warnungen + Notes), chronologisch | | ''errorCount'' | integer | Anzahl Einträge mit ''severity: "error"'' | | ''warningCount'' | integer | Anzahl Einträge mit ''severity: "warning"'' | **Pro Eintrag:** ^ Feld ^ Typ ^ Bedeutung ^ | ''errorId'' | string | Eindeutige ID: ''LYX-S####'' / ''LYX-P####'' / ''LYX-C####'' / ''LYX-W####'' | | ''category'' | ''sema'' / ''parser'' / ''codegen'' | Compiler-Phase, in der der Fehler entstand | | ''severity'' | ''error'' / ''warning'' / ''note'' | Schweregrad | | ''message'' | string | Fehlertext (JSON-escaped) | | ''locations'' | array | Immer genau 1 Eintrag mit ''file'', ''line'', ''col'' | | ''expected'' | null | Reserviert (noch nicht befüllt) | | ''actual'' | null | Reserviert (noch nicht befüllt) | | ''suggestion'' | null | Reserviert (noch nicht befüllt) | | ''astPath'' | null | Reserviert (noch nicht befüllt) | ==== Error-ID-Schema ==== ^ Präfix ^ Kategorie/Schwere ^ Beispiel ^ | ''LYX-S####'' | Sema-Fehler (4-stellige Sequenznummer) | ''LYX-S0001'', ''LYX-S0042'' | | ''LYX-P####'' | Parser-Fehler | ''LYX-P0001'' | | ''LYX-C####'' | Codegen-Fehler | ''LYX-C0001'' | | ''LYX-W####'' | Warnung (unabhängig von Kategorie) | ''LYX-W0001'' | Sequenznummern sind getrennt für Fehler (''errSeq'') und Warnungen (''warnSeq'') und beginnen bei 1. Fehler-IDs sind daher innerhalb einer Compiler-Sitzung eindeutig. ==== Verhalten ==== * ''--error-json'' aktiviert den ''ErrorCollector'' nur für die **Semantic Analysis**. Parse-Fehler werden weiterhin als Text ausgegeben (der Collector wird erst nach dem Parsen initialisiert). * Bei Sema-Fehlern: JSON auf stdout, Exit-Code 1. * Bei Erfolg (kein Fehler): kein JSON-Output, Compilation läuft normal weiter. * ''col'' ist derzeit immer 0 (Spalten-Information aus sema nicht weitergeleitet). * ''file'' ist ''null'' für das Root-Modul; Dateiname bei via ''import'' eingebundenen Modulen. ==== ErrorCollector-Klasse (API) ==== Die Klasse ist in ''src/error_collector.lyx'' als ''pub type ErrorCollector'' definiert: ^ Funktion ^ Beschreibung ^ | ''EcInit()'' | Initialisiert: 64 Records initial (wächst automatisch); 8 192 Byte String-Pool | | ''EcAdd(cat, sev, file, line, msg)'' | Fügt Eintrag hinzu; ''file'' darf 0 (Null-Ptr = Root-Modul) sein; ''msg'' muss NUL-terminiert sein | | ''EcCount(): int64'' | Anzahl der bisher gesammelten Einträge | | ''EcEmitJson(fd: int64)'' | Gibt JSON auf ''fd'' aus (''fd = 1'' für stdout) | Konstanten (''pub con''): ^ Konstante ^ Wert ^ Bedeutung ^ | ''EC_CAT_PARSER'' | 0 | Kategorie: Parser | | ''EC_CAT_SEMA'' | 1 | Kategorie: Semantic Analysis | | ''EC_CAT_CODEGEN'' | 2 | Kategorie: Code-Generierung | | ''EC_SEV_ERROR'' | 0 | Schwere: Fehler | | ''EC_SEV_WARNING'' | 1 | Schwere: Warnung | | ''EC_SEV_NOTE'' | 2 | Schwere: Hinweis | | ''EC_REC_SIZE'' | 80 | Bytes pro Error-Record | Error-Record-Layout (80 Bytes, flat buffer): ^ Offset ^ Feld ^ Bedeutung ^ | +0 | ''category'' | EC_CAT_* | | +8 | ''severity'' | EC_SEV_* | | +16 | ''line'' | Quellzeile | | +24 | ''col'' | Spalte (derzeit immer 0) | | +32 | ''filePtr'' | Zeiger auf externen Dateinamen-String (nicht owned) | | +40 | ''msgOff'' | Offset der Meldung im String-Pool | | +48 | ''msgLen'' | Länge der Meldung | | +56 | ''seqNum'' | 1-basierte Sequenznummer (getrennt für Fehler/Warnungen) | ---- ===== Kombination beider Flags ===== ''%%--%%ast-json'' und ''%%--%%error-json'' schließen sich aus: ''%%--%%ast-json'' beendet vor der Sema, daher gibt es keine Sema-Fehler zu sammeln. Für Linting mit strukturierter Ausgabe: # Nur Sema-Prüfung mit JSON-Fehlerausgabe (kein Binary) lyxc main.lyx --error-json --lint-only # AST ausgeben und anschließend separat auf Fehler prüfen lyxc main.lyx --ast-json > ast.json lyxc main.lyx --error-json > errors.json || true ---- ===== Einsatzbeispiele ===== ==== KI-Agent: alle Funktionsnamen extrahieren ==== lyxc server.lyx --ast-json | jq -r ' .[] | select(.kind == "NK_FUNC_DECL") | .sval ' ==== KI-Agent: public API als Liste ==== lyxc server.lyx --ast-json | jq -r ' [.[] | select(.kind == "NK_FUNC_DECL" and .pub == true) | .sval] | join("\n") ' ==== IDE-Plugin: strukturierte Fehler in Diagnostics umwandeln ==== lyxc main.lyx --error-json 2>/dev/null | jq ' .errors[] | { file: .locations[0].file, line: .locations[0].line, severity: .severity, message: .message } ' ==== Build-System: Fehler zählen ==== result=$(lyxc main.lyx --error-json 2>/dev/null) echo $result | jq '"Fehler: \(.errorCount), Warnungen: \(.warningCount)"' ---- ===== Einschränkungen ===== ^ Thema ^ Details ^ | Parse-Fehler / ''%%--%%error-json'' | Parse-Fehler landen **nicht** im JSON; der ErrorCollector startet erst nach dem Parser | | ''col'' immer 0 | Spaltennummer wird von sema derzeit nicht an EcAdd weitergegeben | | ''expected'' / ''actual'' / ''suggestion'' / ''astPath'' | Felder sind reserviert und immer ''null'' (für spätere Erweiterungen) | | Keine Sema bei ''%%--%%ast-json'' | Der AST zeigt den **Parser-Zustand** — semantische Typen und aufgelöste Symbole fehlen | | Kein ''%%--%%ast-json'' + ''%%--%%error-json'' | Sinnlos kombinierbar, aber ''%%--%%ast-json'' endet vor der Sema (kein Fehler-JSON wird je ausgegeben) | | stderr unverändert | Beide Flags beeinflussen nur stdout; Debug-Ausgaben gehen weiterhin auf stderr | Letzte Aktualisierung: 2026-06-13