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 Prozesses0 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).
  • Für die Basis-Ausgabe braucht es kein importPrintLn, 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.constantsstd/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]; 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<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&lt;T&gt; 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 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. (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