====== Lyx – Erste Schritte ====== Diese Anleitung führt vom leeren Terminal bis zum ersten lauffähigen Lyx-Programm. Keine Vorkenntnisse in Lyx erforderlich — Grundkenntnisse in einer beliebigen Programmiersprache reichen. Alle Beispiele dieser Seite sind mit ''lyxc 1.0.21A'' übersetzt und ausgeführt; die gezeigten Ausgaben sind die echten Programmausgaben. ---- ===== 1. Installation ===== Der Lyx-Compiler ''lyxc'' ist ein einzelnes, autarkes Binary. Er benötigt keine externe C-Runtime, keinen installierten Linker und keine Paketabhängigkeiten. ==== Linux / macOS ==== # Binary herunterladen und ausführbar machen curl -L https://seolizer.de/lyx/releases/latest/lyxc-linux-x86_64 -o lyxc chmod +x lyxc # In PATH verschieben (systemweit) sudo mv lyxc /usr/local/bin/ # Installation prüfen lyxc --version Erwartete Ausgabe: lyxc 1.0.17K Build: bootstrap Ausführlicher meldet sich ''lyxc --build-info'' — Name, Version, Build-Art und Zielplattform. ==== Windows ==== # PowerShell (als Administrator) Invoke-WebRequest https://seolizer.de/lyx/releases/latest/lyxc-windows-x86_64.exe -OutFile lyxc.exe # In PATH eintragen oder direkt aufrufen: .\lyxc.exe --version ==== Unterstützte Zielplattformen ==== Der Compiler läuft immer auf dem Host-System; ''--target'' bestimmt, wofür er erzeugt. Cross-Compilation braucht keine weiteren Werkzeuge. ^ Plattform ^ Flag ^ Ergebnis ^ | Linux x86_64 | ''--target=linux'' (Standard) | ELF 64-bit, x86-64 | | Linux ARM64 | ''--target=arm64'' | ELF 64-bit, AArch64 | | macOS x86_64 | ''--target=macosx64'' | Mach-O 64-bit x86_64 | | macOS Apple Silicon | ''--target=macos-arm64'' | Mach-O 64-bit arm64 | | Windows x86_64 | ''--target=win64'' | PE32+ (Konsole) | | RISC-V 64 | ''--target=riscv'' | ELF 64-bit, RISC-V | | ESP32 (Xtensa) | ''--target=esp32'' | ELF 32-bit, Xtensa | | Android | ''--target=android-arm64'' / ''--target=android-x86_64'' | ELF 64-bit | > **''%%--%%target'' und ''%%--%%arch'' sind zweierlei.** ''xtensa'', ''riscv'' und ''x86_64'' sind Werte für ''%%--%%arch''. Wer ''%%--%%target=xtensa'' schreibt, bekommt **kommentarlos ein x86-64-Binary** — für den ESP32 lautet das Ziel ''esp32''. Geprüft mit 1.0.21A. ---- ===== 2. Hello World ===== Erstelle eine Datei ''hello.lyx'': fn main(): int64 { PrintLn("Hello Lyx!"); return 0; } Kompilieren und ausführen: lyxc hello.lyx -o hello ./hello Hello Lyx! Was hier passiert: * Jedes Lyx-Programm braucht eine Funktion ''main''. Gibt sie ''int64'' zurück, **ist dieser Rückgabewert der Exit-Code des Prozesses** — ''0'' bedeutet Erfolg, alles andere einen Fehler. Der ''_start''-Stub reicht den Wert unverändert an ''exit_group'' weiter; unter Linux kommen davon nur die unteren 8 Bit an (''%%return 256%%'' ergibt also ''0''). Details: [[lyx_-_programmiersprache:sprache:exit-codes|Exit-Codes]]. * ''PrintLn'' gibt einen Text mit Zeilenumbruch aus, ''Print'' ohne. Beide erwarten einen nullterminierten Text (''pchar''). * Für die Basis-Ausgabe braucht es **kein ''import''** — ''PrintLn'', ''Print'', ''PrintF64'' und einige andere sind Builtins. Sobald mehr dazukommt (''IntToStr'', ''StrConcat'', ''FloatToStr''), werden ''std.io'' und ''std.string'' gebraucht. ---- ===== 3. Units und Imports ===== Größere Programme bestehen aus mehreren Dateien. Jede Datei beginnt mit einer **Unit-Deklaration** — dem Namen des Moduls. Andere Units werden mit ''import'' eingebunden. unit greeter; // Name dieser Unit import std.io; // Ein-/Ausgabe import std.math.constants; // PI, E, … fn main(): int64 { Print("Pi ist ungefähr "); PrintF64(PI); return 0; } lyxc greeter.lyx -o greeter ./greeter Pi ist ungefähr 3.141592 > **Es gibt keinen qualifizierten Zugriff.** ''math.PI'' oder ''std.math.PI'' sind keine gültige Schreibweise — der Compiler meldet ''undefined symbol 'math'''. Alle importierten Symbole liegen in **einem flachen Namensraum** und werden unqualifiziert angesprochen: ''PI'', ''SqrtF64'', ''StrConcat''. > > Deshalb tragen die Units Namenspräfixe (''Str*'', ''SQLite*'', ''Pdf*''), und deshalb kollidieren manche Units miteinander — etwa ''std.math'' und ''std.ml'' (beide exportieren ''SqrtF64''). Ein Import zu viel bricht die Übersetzung ab, bevor das Symbol überhaupt benutzt wird. Ein Punktpfad bezeichnet immer genau eine Datei: ''std.math.constants'' → ''std/math/constants.lyx''. Wildcards (''%%import std.audio.*;%%'') und Verzeichnis-Importe (''import std.net;'') gibt es nicht. Ohne ''unit''-Zeile behandelt der Compiler die Datei als anonyme Top-Level-Unit — für ein einzelnes Programm in Ordnung, für alles Wiederverwendbare sollte ein Name stehen. ---- ===== 4. Variablen und Speicherklassen ===== Lyx kennt vier Speicherklassen. Die Wahl ist keine Stilfrage — sie dokumentiert die Absicht und wird vom Compiler geprüft. ^ Schlüsselwort ^ Bedeutung ^ Änderbar? ^ | ''var'' | Veränderliche Variable | Ja | | ''let'' | Einmalig zugewiesener Wert | Nein (nach erstem Wert) | | ''co'' | Konstante zur Laufzeit (Stack) | Nein | | ''con'' | Compile-Zeit-Konstante | Nein — Wert muss zur Übersetzungszeit feststehen | unit main; import std.io; import std.string; con BUFFER_SIZE: int64 := 4096; // Compile-Zeit-Konstante fn main(): int64 { var counter: int64 := 0; // wird verändert let name: pchar := "Lyx"; // einmalig gesetzt co max_retries: int64 := 5; // zur Laufzeit unveränderlich counter := counter + 1; // name := "andere"; // Fehler: let ist unveränderlich PrintLn(name, ": ", IntToStr(counter)); PrintLn("Puffer: ", IntToStr(BUFFER_SIZE)); PrintLn("Versuche: ", IntToStr(max_retries)); return 0; } Lyx: 1 Puffer: 4096 Versuche: 5 Der häufigste Anfängerfehler: '':='' ist Zuweisung, ''=='' ist Vergleich. In einer Bedingung wird ein einzelnes ''='' abgewiesen — ''%%if (x = 10)%%'' ist ein Parse-Fehler. (Als eigenständige Anweisung übersetzt ''%%x = 10;%%'' derzeit noch als Zuweisung, [[https://github.com/SEOLizer/LyX-Compiler/issues/1575|#1575]] — gewöhne dir '':='' trotzdem konsequent an.) var x: int64 := 10; // Zuweisung if (x == 10) { } // Vergleich x := 20; // Zuweisung ---- ===== 5. Grundlegende Datentypen ===== Lyx ist streng typisiert — es gibt keine impliziten Konvertierungen. Jeder Typ muss angegeben oder eindeutig ableitbar sein. ^ Typ ^ Kurzform ^ Beschreibung ^ Beispiel ^ | ''int64'' | ''i64'' | 64-Bit-Ganzzahl mit Vorzeichen | ''%%var x: int64 := 42;%%'' | | ''int32'' | ''i32'' | 32-Bit-Ganzzahl mit Vorzeichen | ''%%var x: int32 := 42;%%'' | | ''uint8'' | ''u8'' | 8 Bit ohne Vorzeichen (Byte) | ''%%var b: uint8 := 200;%%'' | | ''f64'' | – | 64-Bit-Fließkommazahl | ''%%var pi: f64 := 3.14;%%'' | | ''f32'' | – | 32-Bit-Fließkommazahl | ''%%var x: f32 := 1.0;%%'' | | ''bool'' | – | Wahrheitswert | ''%%var ok: bool := true;%%'' | | ''pchar'' | – | nullterminierter Text | ''%%var s: pchar := "Hallo";%%'' | Ein blankes ''int'' gibt es **nicht** — die Breite gehört zum Namen. Vollständig: [[lyx_-_programmiersprache:sprache:datentypen|Datentypen]]. Explizite Umwandlung mit ''as'': unit main; import std.io; fn main(): int64 { var ganzzahl: int64 := 7; var kommazahl: f64 := ganzzahl as f64; // int64 → f64 var gerundet: int64 := kommazahl as int64; // f64 → int64 (schneidet ab) var byte_wert: uint8 := 200; var als_int: int64 := byte_wert as int64; // uint8 → int64 PrintF64(kommazahl); PrintLn(IntToStr(gerundet)); PrintLn(IntToStr(als_int)); return 0; } 7.000000 7 200 ---- ===== 6. Funktionen ===== Funktionen werden mit ''fn'' deklariert; der Rückgabetyp folgt nach dem Doppelpunkt hinter der Parameterliste. unit main; import std.io; import std.string; // Zwei Parameter, ein Rückgabewert fn Add(a: int64, b: int64): int64 { return a + b; } // Ohne Rückgabewert fn PrintLine(msg: pchar): void { PrintLn(msg); } // Mehrere Rückgabewerte als Tupel fn Divide(a: int64, b: int64): (int64, bool) { if (b == 0) { return (0, false); } return (a / b, true); } fn main(): int64 { var sum: int64 := Add(3, 4); PrintLine(StrConcat("Summe: ", IntToStr(sum))); var result, ok := Divide(10, 3); if (ok) { PrintLn("Ergebnis: ", IntToStr(result)); } return 0; } Summe: 7 Ergebnis: 3 Tupel-Rückgaben sind der direkte Weg für „Wert **und** Erfolg" — ohne Zeiger, ohne Out-Parameter, ohne Ausnahmen. Genau zwei Elemente sind möglich, weil die Aufrufkonvention zwei Register trägt. ---- ===== 7. Kontrollfluss ===== ==== if / else ==== fn Classify(n: int64): void { if (n < 0) { PrintLn("negativ"); } else if (n == 0) { PrintLn("null"); } else { PrintLn("positiv"); } } Die Klammern um die Bedingung und die geschweiften Klammern sind **Pflicht**, auch bei einer einzigen Anweisung. ==== Schleifen ==== Es gibt ''while'', ''for … to''/''downto'' und ''repeat … until''. Mit ''limit(N)'' bekommt eine Schleife eine harte Obergrenze — der Compiler kann damit beweisen, dass sie endet. In ''@flight_crit''- und ''@dal(A/B)''-Modulen ist das Pflicht. fn main(): int64 { var i: int64 := 0; while (i < 5) { PrintLn(IntToStr(i)); i := i + 1; } for j := 0 to 4 do { // beide Grenzen einschließlich PrintLn(IntToStr(j)); } var k: int64 := 0; while (k < 100) limit(100) { // nachweisbar endlich k := k + 1; } return 0; } ==== Pattern Matching ==== ''match'' prüft einen Wert gegen Muster und ist erschöpfend — fehlt ein Fall, meldet es der Compiler. unit main; import std.io; fn DayName(day: int64): pchar { match (day) { case 1 => { return "Montag"; } case 2 => { return "Dienstag"; } case 3 => { return "Mittwoch"; } case 6 | 7 => { return "Wochenende"; } case _ => { return "Unbekannt"; } } } fn main(): int64 { PrintLn(DayName(3)); return 0; } Mittwoch ---- ===== 8. Arrays ===== Feste Arrays haben eine zur Übersetzungszeit bekannte Größe und liegen auf dem Stack — keine Allokation, kein Aufräumen. unit main; import std.io; import std.string; fn main(): int64 { var temperatures: [5]f64 := [20.1, 21.3, 19.8, 22.0, 20.5]; var i: int64 := 0; while (i < 5) limit(5) { Print("Messung ", IntToStr(i + 1), ": "); PrintLn(FloatToStr(temperatures[i], 1)); i := i + 1; } var sum: f64 := 0.0; i := 0; while (i < 5) limit(5) { var wert: f64 := temperatures[i]; // erst kopieren, dann rechnen sum := sum + wert; i := i + 1; } Print("Durchschnitt: "); PrintLn(FloatToStr(sum / 5.0, 2)); return 0; } Messung 1: 20.0 Messung 2: 21.2 Messung 3: 19.7 Messung 4: 22.0 Messung 5: 20.5 Durchschnitt: 20.73 > **''f64''-Array-Elemente rechnen seit lyxc 1.0.17K richtig** (#1373). ''%%sum := sum + t[i];%%'' und ''%%t[0] + 1.0%%'' liefern den Wert, nicht mehr das Bitmuster — der Umweg über eine lokale Variable ist nicht mehr nötig (er schadet aber auch nicht). Bis 1.0.17I wurde aus der Summe von ''[20.1, 21.3, 19.8, 22.0, 20.5]'' noch ''4626531008699107328'' statt ''103.7''. Auch zu beachten: ''FloatToStr'' rundet **ab** statt kaufmännisch — ''20.1'' mit einer Nachkommastelle wird zu ''20.0''. Für exakte Ausgabe eine Stelle mehr wählen. ---- ===== 9. Structs ===== Structs fassen zusammengehörige Daten zusammen. Sie liegen auf dem Stack und werden bei der Zuweisung kopiert. unit main; import std.io; import std.math; type Point = struct { x: f64; y: f64; }; fn Distance(a: Point, b: Point): f64 { var dx: f64 := b.x - a.x; var dy: f64 := b.y - a.y; return SqrtF64(dx * dx + dy * dy); } fn main(): int64 { var p1: Point; p1.x := 0.0; p1.y := 0.0; var p2: Point; p2.x := 3.0; p2.y := 4.0; Print("Abstand: "); PrintF64(Distance(p1, p2)); return 0; } Abstand: 5.000000 Die Felder werden **einzeln zugewiesen**; eine Literal-Schreibweise wie ''%%Point { x: 3.0, y: 4.0 }%%'' kennt der Parser nicht. Die Wurzelfunktion heißt ''SqrtF64'', nicht ''Sqrt'' — die Fließkomma-Funktionen aus ''std.math'' tragen durchgehend das Suffix ''F64''. ---- ===== 10. Der Pipe-Operator ===== ''%%|>%%'' reicht den linken Wert als **erstes Argument** an die nächste Funktion weiter. Das macht Verarbeitungsketten von links nach rechts lesbar. unit main; import std.io; fn Double(x: f64): f64 { return x * 2.0; } fn Square(x: f64): f64 { return x * x; } fn Negate(x: f64): f64 { return -x; } fn main(): int64 { // Ohne Pipe: von innen nach außen zu lesen var result1: f64 := Negate(Square(Double(3.0))); // Mit Pipe: von links nach rechts var result2: f64 := 3.0 |> Double() |> Square() |> Negate(); // 3.0 → 6.0 → 36.0 → -36.0 PrintF64(result2); return 0; } -36.000000 Weitere Argumente sind erlaubt — ''%%50 |> Clamp(0, 30)%%'' bedeutet ''Clamp(50, 0, 30)''. Soll der Wert an eine andere Stelle, markiert ihn ein ''?'': ''%%50 |> Clamp(0, ?, 30)%%''. ---- ===== 11. Speicherverwaltung ===== Lyx hat keinen Garbage Collector. Der Stack reicht für die meisten Fälle; Heap-Speicher wird ausdrücklich angefordert und wieder freigegeben. unit main; import std.io; import std.string; fn main(): int64 { // Stack — automatisch, kein Aufräumen var werte: [256]int64; // Heap — dynamisches Array var liste: array := []; liste.push(42); liste.push(17); PrintLn("Einträge: ", IntToStr(len(liste))); dispose liste; // Freigabe return 0; } Einträge: 2 Faustregel: Größe zur Übersetzungszeit bekannt und unter etwa 100 KB → Stack (''[N]T''). Dynamisch → ''array<T>'' oder ''%%new T[n]%%'' mit ''dispose''. Rohe Puffer holt man mit ''alloc(n)'' und gibt sie mit ''free(ptr, size)'' zurück → [[lyx_-_programmiersprache:sprache:memory-management|Memory Management]]. ---- ===== 12. Wichtige Compiler-Flags ===== ^ Flag ^ Wirkung ^ | ''lyxc datei.lyx -o programm'' | Übersetzen und linken in einem Schritt | | ''lyxc datei.lyx -I ./src'' | Suchpfad für eigene Units | | ''lyxc datei.lyx --runtime-checks'' | Bereichs- und Nullprüfungen zur Laufzeit einschalten | | ''lyxc datei.lyx --lint'' | Stil- und Safety-Warnungen | | ''lyxc datei.lyx --lint-only'' | Nur prüfen, kein Binary | | ''lyxc datei.lyx --static-analysis'' | Datenfluss, lebende Variablen, Stack | | ''lyxc datei.lyx --call-graph'' | Aufrufgraph, Rekursionserkennung | | ''lyxc datei.lyx --target=arm64'' | Für ARM64 übersetzen | | ''lyxc datei.lyx --compile-unit -o datei.lyu'' | Unit vorkompilieren (''-o'' ist Pflicht) | | ''lyxc --version'' / ''--build-info'' | Version und Build-Konfiguration | **Ohne ''%%--%%runtime-checks'' wird ein Zugriff außerhalb der Array-Grenzen nicht bemerkt** — der Zugriff liest dann kommentarlos fremden Speicher. Vollständig: [[lyx_-_programmiersprache:tools:compiler-parameter|Compiler-Parameter]]. ---- ===== 13. Vollständiges Beispiel: Temperatur-Logger ===== Dieses Beispiel verbindet die Grundkonzepte: Unit, Konstante, Struct, Funktionen, Schleife mit ''limit'', Ausgabe. unit main; import std.io; import std.string; con MAX_READINGS: int64 := 10; type Reading = struct { sensor_id: int64; temperature: f64; alarm: bool; }; fn MakeReading(id: int64, temp: f64): Reading { var alarm_temp: f64 := 85.0; // lokal halten — siehe Kasten unten var r: Reading; r.sensor_id := id; r.temperature := temp; r.alarm := temp >= alarm_temp; return r; } fn PrintReading(r: Reading): void { Print("Sensor ", IntToStr(r.sensor_id), ": "); Print(FloatToStr(r.temperature, 1)); Print(" Grad"); if (r.alarm) { Print(" *** ALARM ***"); } PrintLn(""); } fn main(): int64 { var temps: [10]f64 := [ 72.3, 75.1, 79.8, 83.2, 86.0, 88.5, 84.1, 80.3, 77.9, 74.2 ]; var alarm_count: int64 := 0; var i: int64 := 0; while (i < MAX_READINGS) limit(MAX_READINGS) { var r: Reading := MakeReading(i + 1, temps[i]); PrintReading(r); if (r.alarm) { alarm_count := alarm_count + 1; } i := i + 1; } PrintLn(""); PrintLn("Alarm-Ereignisse: ", IntToStr(alarm_count), " von 10 Messungen"); return 0; } lyxc temp_logger.lyx -o temp_logger ./temp_logger Sensor 1: 72.2 Grad Sensor 2: 75.0 Grad Sensor 3: 79.7 Grad Sensor 4: 83.1 Grad Sensor 5: 86.0 Grad *** ALARM *** Sensor 6: 88.5 Grad *** ALARM *** Sensor 7: 84.0 Grad Sensor 8: 80.2 Grad Sensor 9: 77.8 Grad Sensor 10: 74.1 Grad Alarm-Ereignisse: 2 von 10 Messungen > **Fließkomma-Schwellwerte nicht als globales ''con'' ablegen.** Globale ''var''- und ''co''-Variablen vom Typ ''f64'' rechnen seit lyxc 1.0.17K richtig; ''con'' nicht. Mit ''%%con ALARM_TEMP: f64 := 85.0;%%'' ist ''%%86.0 >= ALARM_TEMP%%'' **falsch**, und ''%%ALARM_TEMP + 0.0%%'' ergibt ''4635681760191971328'' — die Konstante kommt als Bitmuster in die Rechnung. Die direkte Ausgabe mit ''PrintF64'' stimmt dagegen, der Fehler fällt deshalb leicht nicht auf. > > Bis das behoben ist: den Schwellwert **lokal** anlegen (wie oben), als globales ''var''/''co'' schreiben oder die Konstante zuerst in eine lokale Variable kopieren. Ganzzahlige Globals (''%%con MAX_READINGS: int64%%'') sind nicht betroffen. ([[https://github.com/SEOLizer/LyX-Compiler/issues/1373|Issue #1373]], geprüft mit 1.0.17K) ---- ===== Nächste Schritte ===== ^ Thema ^ Seite ^ | Die Sprache im Überblick, mit Lesepfaden | [[lyx_-_programmiersprache:sprache|Sprachreferenz]] | | Alle Sprachkonstrukte im Detail | [[lyx_-_programmiersprache:sprache:syntax|Syntax-Referenz]] | | Variablen: Speicherklassen, Gültigkeit, Lebensdauer | [[lyx_-_programmiersprache:sprache:variablen|Variablen]] | | Datentypen vollständig | [[lyx_-_programmiersprache:sprache:datentypen|Datentypen]] | | Kontrollfluss und Schleifen | [[lyx_-_programmiersprache:sprache:schleifen|Schleifen]] | | Objektorientierung | [[lyx_-_programmiersprache:sprache:oop|OOP – Klassen & Vererbung]] | | Interfaces und generische Funktionen | [[lyx_-_programmiersprache:sprache:generics-traits|Generics & Interfaces]] | | Speicherverwaltung | [[lyx_-_programmiersprache:sprache:memory-management|Memory Management]] | | Rohspeicher (alloc/peek/poke) | [[lyx_-_programmiersprache:sprache:rohspeicher|Rohspeicher]] | | Fehlerkonventionen der Stdlib | [[lyx_-_programmiersprache:sprache:std-fehlerkonventionen|Std-Fehlerkonventionen]] | | Die passende Unit finden | [[lyx_-_programmiersprache:guides:welche-unit|Welche Unit?]] | | Standardbibliothek | [[lyx_-_programmiersprache:units|Standardbibliothek – Übersicht]] | | Safety-Entwicklung | [[lyx_-_programmiersprache:guides:aerospace-safety|Aerospace & Safety]] | Letzte Aktualisierung: 2026-08-13 · alle Beispiele geprüft mit ''lyxc 1.0.21A''; Kasten zu ''f64''-Array-Elementen aufgelöst (behoben, #1373), Kasten zu globalen Fließkomma-Schwellwerten auf ''con'' eingegrenzt