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.
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 version: 0.8.5-aerospace
Target: x86_64-linux-elf
Reproducible: yes
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
| Plattform | Flag |
|---|---|
| Linux x86_64 | –target=x86_64 (Default) |
| Linux ARM64 | –target=arm64 |
| macOS x86_64 | –target=macos-x86_64 |
| macOS Apple Silicon | –target=macos-arm64 |
| RISC-V 64 | –target=riscv64 |
| ESP32 (Xtensa) | –target=xtensa |
Der Compiler läuft immer auf dem Host-System — Cross-Compilation auf eine andere Zielarchitektur ist über –target möglich, ohne weitere Tools.
2. Hello World
Erstelle eine Datei hello.lyx:
fn main(): int64 {
PrintStr("Hello Lyx!\n");
return 0;
}
Kompilieren und ausführen:
lyxc hello.lyx -o hello
./hello
Hello Lyx!
Was hier passiert:
- Jedes Lyx-Programm braucht eine Funktion
main, dieint64zurückgibt. Der Rückgabewert ist der Exit-Code des Prozesses —0bedeutet Erfolg. PrintStrist eine eingebaute Ausgabefunktion. Sie erwartet einen nullterminierten String.- Kein
importnötig für die Basis-Ausgabefunktionen — sie sind Teil der impliziten Laufzeitumgebung.
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; // Standard-Ausgabe und Eingabe
import std.math; // Mathematische Funktionen
fn main(): int64 {
PrintStr("Pi ist ungefähr ");
PrintF64(math.PI);
PrintStr("\n");
return 0;
}
lyxc greeter.lyx -o greeter
./greeter
Pi ist ungefähr 3.141593
Ohne unit-Deklaration behandelt der Compiler die Datei als anonyme Top-Level-Unit. Für einzelne Programmdateien ist das in Ordnung — für wiederverwendbare Bibliotheken sollte immer ein Name angegeben werden.
4. Variablen und Speicherklassen
Lyx kennt vier Speicherklassen. Die Wahl der richtigen Speicherklasse ist keine Stilfrage — sie ist Dokumentation der Absicht und wird vom Compiler überprüft.
| Schlüsselwort | Bedeutung | Änderbar? |
|---|---|---|
var | Veränderliche Variable | Ja |
let | Einmalig zugewiesener Wert | Nein (nach erstem Wert) |
co | Konfigurationswert (Runtime-Konstante) | Nein |
con | Compile-Zeit-Konstante | Nein — Wert muss zur Compile-Zeit bekannt sein |
fn main(): int64 {
var counter: int64 := 0; // Zähler — wird verändert
let name: int64 := "Lyx"; // Einmalig gesetzt, danach unveränderlich
co max_retries: int64 := 5; // Konfigurationswert — unveränderlich zur Laufzeit
con BUFFER_SIZE: int64 := 4096; // Compile-Zeit-Konstante — kein Speicher zur Laufzeit
counter := counter + 1; // OK
// name := "andere"; // Compiler-Fehler: let-Variable ist unveränderlich
PrintInt(counter);
PrintStr("\n");
return 0;
}
Der häufigste Fehler für Einsteiger: := ist Zuweisung, = ist Vergleich.
var x: int64 := 10; // Zuweisung: x bekommt den Wert 10
if (x = 10) { ... } // Vergleich: ist x gleich 10?
x := 20; // Zuweisung: x bekommt den Wert 20
5. Grundlegende Datentypen
Lyx ist streng typisiert — es gibt keine impliziten Konvertierungen. Jeder Typ muss explizit angegeben oder eindeutig inferiert werden.
| Typ | Beschreibung | Beispiel |
|---|---|---|
int64 | 64-Bit-Ganzzahl (signed) | var x: int64 := 42 |
int32 | 32-Bit-Ganzzahl (signed) | var x: int32 := 42i32 |
uint8 | 8-Bit vorzeichenlos (Byte) | var b: uint8 := 0xFFu8 |
f64 | 64-Bit-Fließkommazahl | var pi: f64 := 3.14 |
f32 | 32-Bit-Fließkommazahl | var x: f32 := 1.0f32 |
bool | Wahrheitswert | var ok: bool := true |
pchar | Zeiger auf nullterminierten String | var s: pchar := „Hallo“ |
Explizite Typkonvertierung mit as:
fn main(): int64 {
var ganzzahl: int64 := 7;
var kommazahl: f64 := ganzzahl as f64; // int64 → f64
var gerundet: int64 := kommazahl as int64; // f64 → int64 (abgeschnitten)
var byte_wert: uint8 := 200u8;
var als_int: int64 := byte_wert as int64; // uint8 → int64
PrintF64(kommazahl);
PrintStr("\n");
return 0;
}
6. Funktionen
Funktionen werden mit fn deklariert. Der Rückgabetyp folgt nach dem Doppelpunkt hinter der Parameterliste.
// Einfache Funktion mit zwei Parametern
fn Add(a: int64, b: int64): int64 {
return a + b;
}
// Funktion ohne Rückgabewert
fn PrintLine(msg: int64): void {
PrintStr(msg);
PrintStr("\n");
}
// Funktion mit mehreren Rückgabewerten (Tuple)
fn Divide(a: int64, b: int64): (int64, bool) {
if (b == 0) {
return (0, false); // Fehlerfall
}
return (a / b, true); // Ergebnis und Erfolg
}
fn main(): int64 {
var sum: int64 := Add(3, 4);
PrintLine("Summe berechnet");
var (result, ok): (int64, bool) := Divide(10, 3);
if (ok) {
PrintStr("Ergebnis: ");
PrintInt(result);
PrintStr("\n");
}
return 0;
}
Tuple-Rückgaben sind ein direkter Weg, mehrere Werte zurückzugeben — ohne Zeiger, ohne Out-Parameter, ohne Fehler-Exceptions.
7. Kontrollfluss
if / else
fn Classify(n: int64): void {
if (n < 0) {
PrintStr("negativ\n");
} else if (n == 0) {
PrintStr("null\n");
} else {
PrintStr("positiv\n");
}
}
while-Schleife
Die Standard-Schleife in Lyx. Für sicherheitskritischen Code kann mit limit(N) ein hartes Maximum an Iterationen gesetzt werden — der Compiler kann dann die Endlichkeit statisch nachweisen.
fn main(): int64 {
var i: int64 := 0;
// Normale while-Schleife
while (i < 5) {
PrintInt(i);
PrintStr("\n");
i := i + 1;
}
// Schleife mit Iterations-Limit (für Safety-Code)
var j: int64 := 0;
while (j < 100) limit(100) {
j := j + 1;
}
return 0;
}
Pattern Matching
match wertet einen Ausdruck gegen Muster aus und ist erschöpfend — der Compiler prüft, ob alle möglichen Fälle abgedeckt sind.
fn DayName(day: int64): int64 {
match day {
1 => return "Montag";
2 => return "Dienstag";
3 => return "Mittwoch";
4 => return "Donnerstag";
5 => return "Freitag";
6 => return "Samstag";
7 => return "Sonntag";
_ => return "Unbekannt";
}
}
fn main(): int64 {
PrintStr(DayName(3));
PrintStr("\n");
return 0;
}
8. Arrays
Arrays haben eine feste Größe, die zur Compile-Zeit bekannt sein muss. Sie liegen auf dem Stack — kein Heap, keine versteckte Allokation.
fn main(): int64 {
var temperatures: f64[5] := [20.1, 21.3, 19.8, 22.0, 20.5];
// Elemente ausgeben
var i: int64 := 0;
while (i < 5) limit(5) {
PrintStr("Messung ");
PrintInt(i + 1);
PrintStr(": ");
PrintF64(temperatures[i]);
PrintStr(" °C\n");
i := i + 1;
}
// Summe berechnen
var sum: f64 := 0.0;
i := 0;
while (i < 5) limit(5) {
sum := sum + temperatures[i];
i := i + 1;
}
PrintStr("Durchschnitt: ");
PrintF64(sum / 5.0);
PrintStr(" °C\n");
return 0;
}
9. Structs
Structs fassen zusammengehörige Daten unter einem gemeinsamen Namen zusammen.
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 math.Sqrt(dx * dx + dy * dy);
}
fn main(): int64 {
var p1: Point := Point { x: 0.0, y: 0.0 };
var p2: Point := Point { x: 3.0, y: 4.0 };
PrintStr("Abstand: ");
PrintF64(Distance(p1, p2));
PrintStr("\n");
// Ausgabe: 5.0
return 0;
}
10. Der Pipe-Operator
|> leitet den Ergebniswert eines Ausdrucks als erstes Argument an die nächste Funktion weiter. Das macht Datenpipelines lesbar — von links nach rechts, ohne verschachtelte Funktionsaufrufe.
import std.math;
import std.string;
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: schwer 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);
PrintStr("\n");
return 0;
}
11. Speicherverwaltung
Lyx hat keinen Garbage Collector. Speicher auf dem Heap wird mit new angefordert und muss mit dispose freigegeben werden. Für die meisten Anwendungsfälle reicht der Stack aus.
fn main(): int64 {
// Stack — automatisch, kein Aufräumen nötig
var stack_array: int64[256]; // 256 × 8 Byte auf dem Stack
// Heap — manuell verwaltet
var heap_ptr: int64 := new int64[1000]; // 1000 × 8 Byte auf dem Heap
// ... Arbeit mit heap_ptr ...
dispose heap_ptr; // Freigabe — sonst Memory Leak
return 0;
}
Faustregel: Wenn die Größe zur Compile-Zeit bekannt ist und unter ~100 KB liegt — Stack. Wenn dynamisch oder groß — Heap mit new + dispose.
12. Wichtige Compiler-Flags
| Flag | Wirkung |
|---|---|
lyxc datei.lyx -o programm | Kompiliert und linkt zu einem Binary |
lyxc datei.lyx –lint | Prüft auf Stil- und Safety-Verstöße |
lyxc datei.lyx –lint-only | Nur Lint, kein Binary |
lyxc datei.lyx –static-analysis | Tiefe statische Analyse |
lyxc datei.lyx –target=arm64 | Cross-Compilation für ARM64 |
lyxc datei.lyx –compile-unit -o datei.lyu | Vorkompilierte Unit erzeugen |
lyxc datei.lyx -I ./build/ | Suchpfad für vorkompilierte Units (.lyu) |
lyxc datei.lyx –emit-asm | Assembler-Ausgabe (zur Analyse) |
lyxc –version | Compiler-Version anzeigen |
lyxc –build-info | Detaillierte Build-Konfiguration |
13. Vollständiges Beispiel: Temperatur-Logger
Dieses Beispiel kombiniert alle Grundkonzepte: Unit-Deklaration, Struct, Funktionen, Schleife, Ausgabe.
unit TempLogger;
import std.io;
// Konfiguration — Compile-Zeit-Konstanten
con MAX_READINGS: int64 := 10;
con ALARM_TEMP: f64 := 85.0;
// Datenstruktur für eine Messung
type Reading = struct {
sensor_id: int64;
temperature: f64;
alarm: bool;
};
// Erstellt eine Messung und setzt den Alarm-Status
fn MakeReading(id: int64, temp: f64): Reading {
return Reading {
sensor_id: id,
temperature: temp,
alarm: temp >= ALARM_TEMP
};
}
// Gibt eine Messung formatiert aus
fn PrintReading(r: Reading): void {
PrintStr("Sensor ");
PrintInt(r.sensor_id);
PrintStr(": ");
PrintF64(r.temperature);
PrintStr(" °C");
if (r.alarm) {
PrintStr(" *** ALARM ***");
}
PrintStr("\n");
}
fn main(): int64 {
// Simulierte Messwerte
var temps: f64[10] := [
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;
}
PrintStr("\nAlarm-Ereignisse: ");
PrintInt(alarm_count);
PrintStr(" von ");
PrintInt(MAX_READINGS);
PrintStr(" Messungen\n");
return 0;
}
lyxc temp_logger.lyx -o temp_logger
./temp_logger
Sensor 1: 72.300000 °C
Sensor 2: 75.100000 °C
Sensor 3: 79.800000 °C
Sensor 4: 83.200000 °C
Sensor 5: 86.000000 °C *** ALARM ***
Sensor 6: 88.500000 °C *** ALARM ***
Sensor 7: 84.100000 °C
Sensor 8: 80.300000 °C
Sensor 9: 77.900000 °C
Sensor 10: 74.200000 °C
Alarm-Ereignisse: 2 von 10 Messungen
Nächste Schritte
| Thema | Seite |
|---|---|
| Alle Sprachkonstrukte im Detail | Syntax-Referenz |
| Datentypen vollständig | Datentypen |
| Kontrollfluss und Schleifen | Schleifen |
| Objektorientierung | OOP – Klassen & Vererbung |
| Generics & Traits | Generics & Traits |
| Speicherverwaltung | Memory Management |
| Standardbibliothek | Standardbibliothek – Übersicht |
| Safety-Entwicklung | Aerospace & Safety |
| Aerospace-Tutorial | Von der Anforderung zum Nachweis |
