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 |
--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.
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 sieint64zurück, ist dieser Rückgabewert der Exit-Code des Prozesses —0bedeutet Erfolg, alles andere einen Fehler. Der_start-Stub reicht den Wert unverändert anexit_groupweiter; unter Linux kommen davon nur die unteren 8 Bit an (return 256ergibt also0). Details: Exit-Codes. PrintLngibt einen Text mit Zeilenumbruch aus,Printohne. Beide erwarten einen nullterminierten Text (pchar).- Für die Basis-Ausgabe braucht es kein
import—PrintLn,Print,PrintF64und einige andere sind Builtins. Sobald mehr dazukommt (IntToStr,StrConcat,FloatToStr), werdenstd.ioundstd.stringgebraucht.
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.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.
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, #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: 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];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.
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<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.
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: 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 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)
Nächste Schritte
| 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-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
