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, die int64 zurückgibt. Der Rückgabewert ist der Exit-Code des Prozesses — 0 bedeutet Erfolg.
  • PrintStr ist eine eingebaute Ausgabefunktion. Sie erwartet einen nullterminierten String.
  • Kein import nö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