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.
→ Tools & Compiler · 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<T> |
| 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-jsonaktiviert denErrorCollectornur 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.
colist derzeit immer 0 (Spalten-Information aus sema nicht weitergeleitet).fileistnullfür das Root-Modul; Dateiname bei viaimporteingebundenen 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
