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.1.14A übersetzt und ausgeführt; die gezeigten Ausgaben sind die echten Programmausgaben.
Der Lyx-Compiler lyxc ist ein einzelnes, autarkes Binary. Er benötigt keine externe C-Runtime, keinen installierten Linker und keine Paketabhängigkeiten.
# 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.
# 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
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 |
--targetund--archsind zweierlei.xtensa,riscvundx86_64sind Werte für--arch. Wer--target=xtensaschreibt, bekommt kommentarlos ein x86-64-Binary — für den ESP32 lautet das Zielesp32. Geprüft mit 1.0.21A.
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:
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: Exit-Codes.PrintLn gibt einen Text mit Zeilenumbruch aus, Print ohne. Beide erwarten einen nullterminierten Text (pchar).import — PrintLn, Print, PrintF64 und einige andere sind Builtins. Sobald mehr dazukommt (IntToStr, StrConcat, FloatToStr), werden std.io und std.string gebraucht.
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.PIoderstd.math.PIsind keine gültige Schreibweise — der Compiler meldetundefined 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 — etwastd.mathundstd.ml(beide exportierenSqrtF64). 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.
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, #1575 — gewöhne dir := trotzdem konsequent an.)
var x: int64 := 10; // Zuweisung
if (x == 10) { } // Vergleich
x := 20; // Zuweisung
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: 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
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.
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.
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;
}
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
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.1
Messung 2: 21.3
Messung 3: 19.8
Messung 4: 22.0
Messung 5: 20.5
Durchschnitt: 20.74
f64-Array-Elemente rechnen seit lyxc 1.0.17K richtig (#1373).sum := sum + t[i];undt[0] + 1.0liefern 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]noch4626531008699107328statt103.7.
FloatToStr rundet inzwischen kaufmännisch: 20.1 mit einer Nachkommastelle bleibt 20.1, 19.75 wird 19.8, 19.74 wird 19.7 (gemessen mit 1.1.14A). Bis 1.0.21A schnitt die Funktion ab — ältere Fassungen dieser Seite zeigen deshalb 20.0 und einen Durchschnitt von 20.73.
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.
|> 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).
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<int64> := [];
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 → Memory Management.
| 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: Compiler-Parameter.
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 globalesconablegen. Globalevar- undco-Variablen vom Typf64rechnen seit lyxc 1.0.17K richtig;connicht. Mitcon ALARM_TEMP: f64 := 85.0;ist86.0 >= ALARM_TEMPfalsch, undALARM_TEMP + 0.0ergibt4635681760191971328— die Konstante kommt als Bitmuster in die Rechnung. Die direkte Ausgabe mitPrintF64stimmt dagegen, der Fehler fällt deshalb leicht nicht auf.
Bis das behoben ist: den Schwellwert lokal anlegen (wie oben), als globalesvar/coschreiben oder die Konstante zuerst in eine lokale Variable kopieren. Ganzzahlige Globals (con MAX_READINGS: int64) sind nicht betroffen. (Issue #1373, geprüft mit 1.0.17K)
| Thema | Seite |
|---|---|
| Die Sprache im Überblick, mit Lesepfaden | Sprachreferenz |
| Alle Sprachkonstrukte im Detail | Syntax-Referenz |
| Variablen: Speicherklassen, Gültigkeit, Lebensdauer | Variablen |
| Datentypen vollständig | Datentypen |
| Kontrollfluss und Schleifen | Schleifen |
| Objektorientierung | OOP – Klassen & Vererbung |
| Interfaces und generische Funktionen | Generics & Interfaces |
| Speicherverwaltung | Memory Management |
| Rohspeicher (alloc/peek/poke) | Rohspeicher |
| Fehlerkonventionen der Stdlib | Std-Fehlerkonventionen |
| Die passende Unit finden | Welche Unit? |
| Standardbibliothek | Standardbibliothek – Übersicht |
| Safety-Entwicklung | Aerospace & Safety |
Letzte Aktualisierung: 2026-08-30 · alle Beispiele gegen lyxc 1.1.14A übersetzt und ausgeführt; Ausgabe des Temperatur-Beispiels und der Hinweis zu FloatToStr berichtigt (rundet jetzt kaufmännisch)
Vorherige 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
Codebeispiele geprüft: gegen lyxc 1.2.5C übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die pub fn-Signaturen in aurum/std gehalten, 0 Abweichungen).