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-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