Native • Statisch typisiert • Multi-Plattform • Embedded • Aerospace • High Performance
| Version | v0.8.5-aerospace |
|---|---|
| Compiler | lyxc |
| Status | Aktive Entwicklung |
| Autor | Andreas Röne |
Lyx ist eine moderne, nativ kompilierende Programmiersprache, die von Grund auf für zwei Welten entworfen wurde: leistungsstarke Systemprogrammierung und zertifizierungsfähige, sicherheitskritische Software nach DO-178C.
Der Compiler erzeugt direkt ausführbare Programme für x86_64, ARM64, RISC-V und ESP32 — ohne virtuelle Maschine, ohne Bytecode und ohne externe Runtime-Abhängigkeiten. Alle Standardbibliotheken (std/ und data/) sind in nativem Lyx geschrieben; es existieren keine Abhängigkeiten zu C-Bibliotheken oder anderen Laufzeitsystemen.
Für sicherheitskritische Anwendungen bietet Lyx eine integrierte Safety-Toolchain: Sicherheits-Pragmas (@flight_crit, @redundant, @stack_limit, @wcet, …), Range-Types mit Compile-Time- und Runtime-Prüfung, MC/DC-Instrumentierung und deterministische, reproduzierbare Builds. Diese Eigenschaften machen Lyx zu einer geeigneten Basis für Software nach DAL-A bis DAL-E gemäß DO-178C.
fn main(): int64 {
PrintStr("Hello Lyx!\n");
return 0;
}
Kompilieren:
lyxc hello.lyx -o hello
./hello
Lyx unterscheidet vier Speicherklassen, weil Veränderbarkeit eine bewusste Entscheidung sein soll — nicht eine Standardannahme. var markiert eine veränderbare Variable, let ist nach der ersten Zuweisung unveränderbar, co ist ein schreibgeschützter Stack-Slot, dessen Wert erst zur Laufzeit feststeht, und con ist eine echte Compile-Zeit-Konstante, die direkt als Immediate in den Maschinencode eingebettet wird.
Der Vorteil ist doppelt: Der Compiler kann Optimierungen aggressiver anwenden, wenn er weiß, was sich nie ändert. Und im Code ist auf den ersten Blick erkennbar, welche Daten fließen und welche feststehen — das ist besonders in sicherheitskritischem Code und bei Code-Reviews von hohem Wert.
var counter: int64 := 42;
let maximum: int64 := 100;
co runtimeConst: int64 := 5;
con compileConst: int64 := 10;
Funktionen in Lyx verlangen explizite Typangaben für alle Parameter und den Rückgabetyp. Das ist eine bewusste Entscheidung gegen vollständige Typ-Inferenz bei Schnittstellen: Eine Funktion ist ein Vertrag zwischen Aufrufer und Implementierung, und dieser Vertrag soll im Quellcode lesbar sein — ohne Compiler-Lauf oder IDE.
Das erleichtert Code-Reviews, macht Schnittstellen selbstdokumentierend und ist Voraussetzung für DO-178C-konforme Softwarenachweise, bei denen Schnittstellenspezifikationen prüfbar dokumentiert sein müssen.
fn add(a: int64, b: int64): int64 {
return a + b;
}
→ Funktionen (vertieft) — Tupel-Rückgabe, anonyme Funktionen, Higher-Order Functions, Pipe-Operator
Bedingungen nutzen die bekannte if/else-Struktur mit Klammerung der Bedingung. Lyx trennt strikt zwischen Zuweisung ( := ) und Vergleich ( == ), was den häufigen C-Fehler if (x = 0) strukturell unmöglich macht — eine Zuweisung ist kein gültiger Ausdruck in einer Bedingung.
Das ist kein stilistisches Detail, sondern ein sicherheitsrelevantes Design: In Systemen, die nach DO-178C zertifiziert werden, ist jede Quelle versehentlicher Seiteneffekte in Bedingungen ein potenzieI kritischer Fehler.
if (value > 0) {
PrintStr("positive\n");
}
else {
PrintStr("negative\n");
}
while ist die grundlegende Schleifenform für bedingte Wiederholung. Lyx bietet zusätzlich for i := 1 to N und repeat … until für häufige Muster. Die explizite Abbruchbedingung ist bewusst sichtbar gehalten — kein implizites Iterator-Protokoll, das versteckt, wann die Schleife endet.
In sicherheitskritischer Software ist die Endlichkeit von Schleifen eine Nachweispflicht. Explizite Bedingungen vereinfachen die Worst-Case-Execution-Time-Analyse (WCET) erheblich, weil das Analysetool die Bedingung direkt auswerten kann.
while (i < 10) {
PrintInt(i);
i := i + 1;
}
Arrays in Lyx sind Fat Pointers: Sie speichern intern Basisadresse, aktuelle Länge und Kapazität. Der Zugriff per Index ist syntaktisch direkt, und Zuweisungen über den Index sind klar von Lesezugriffen unterscheidbar. Dynamische Arrays wachsen bei Bedarf automatisch nach.
Für sicherheitskritische Systeme empfehlen sich statische Arrays mit fester Größe (var buf: uint8[256]), da diese vollständig auf dem Stack liegen und keinen Heap-Druck erzeugen. Dynamische Arrays sind ideal für Server- und Tool-Code, wo Flexibilität wichtiger ist als deterministisches Speicherverhalten.
var values := [10, 20, 30];
PrintInt(values[0]);
values[1] := 50;
Enums bündeln zusammengehörige benannte Ganzzahl-Konstanten unter einem gemeinsamen Typ. Anstatt „Magic Numbers“ wie 0, 1, 2 im Code zu verwenden, gibt es sprechende Namen wie Status::Ok oder Status::Error. Intern werden Enum-Werte als int64 abgebildet, was die FFI-Nutzung und das Logging vereinfacht.
Der wesentliche Vorteil ist Lesbarkeit und Refactoring-Sicherheit: Wenn sich ein Wert ändert, ändert man ihn an einer Stelle — nicht an zwanzig Stellen im Code, an denen 2 für „Error“ stand.
enum Status {
Ok,
Warning,
Error
}
var s: int64 := Status::Ok;
Manchmal muss eine Funktion mehrere zusammengehörige Werte gleichzeitig zurückgeben — zum Beispiel Quotient und Rest einer Division, oder einen Wert zusammen mit einem Fehlercode. Ohne Tuple-Rückgaben wären dafür entweder Output-Parameter (unschön) oder ein eigens definierter Wrapper-Struct (Overhead) nötig.
Lyx erlaubt Tupel direkt in der Funktionssignatur und destrukturiert sie bei der Zuweisung. Das Ergebnis ist kompakter, selbsterklärender Code ohne zusätzliche Typdefinitionen für einfache Multi-Rückgaben.
fn divmod(a: int64, b: int64): (int64, int64) {
return (a / b, a % b);
}
var quotient, remainder := divmod(17, 5);
→ Funktionen (vertieft) — vollständige Dokumentation zu Tupel-Rückgabe und Destrukturierung
Structs sind leichtgewichtige, stackbasierte Datencontainer. Sie können Methoden besitzen, unterstützen aber keine Vererbung. Zuweisung kopiert den gesamten Inhalt (Copy-by-Value) — es gibt keinen verborgenen Heap-Zugriff und keine Referenzzählung.
Das ist die bevorzugte Datenstruktur für kleine, kurzlebige Objekte wie Vektoren, Punkte, Farben oder Sensorwerte. Zero-Overhead, kein GC-Druck, vollständig deterministisch — genau das, was Embedded- und Aerospace-Code verlangt.
type Point = struct {
x: int64;
y: int64;
fn lengthSquared(): int64 {
return self.x * self.x +
self.y * self.y;
}
};
Klassen sind für Objekte, die Polymorphismus, Vererbung und eine längere Lebensdauer als ihren Erzeugungskontext brauchen. Sie werden auf dem Heap angelegt (new) und müssen explizit freigegeben werden (dispose). Virtuelle Methoden ermöglichen, dass verschiedene Untertypen austauschbar verwendet werden können.
Lyx trennt bewusst Structs und Klassen, damit die Performance-Implikationen — Heap-Allokation und V-Table-Lookup — im Code sichtbar bleiben. In DAL-A Systemen sollten Klassen nur in der Initialisierungsphase instanziiert werden, nicht im laufenden Regelzyklus.
type Animal = class {
virtual fn speak() {
PrintStr("sound\n");
}
};
type Dog = class extends Animal {
override fn speak() {
PrintStr("woof\n");
}
};
Generics ermöglichen es, eine Funktion oder Datenstruktur einmal zu schreiben und für beliebige Typen zu verwenden. Der Compiler erzeugt per Monomorphisierung für jeden genutzten Typ eine eigene optimierte Version — es gibt keinen Runtime-Overhead, keine Typ-Erasure und keine Boxed Values wie in manchen anderen Sprachen.
Das Ergebnis ist DRY-Code (Don't Repeat Yourself) ohne Kompromisse bei der Performance. Für Lyx besonders relevant: Statische Generics sind WCET-berechenbar, weil alle Sprungziele zur Compile-Zeit bekannt sind.
fn max[T](a: T, b: T): T {
if (a > b) {
return a;
}
return b;
}
Pattern Matching ist eine leistungsfähigere Fallunterscheidung als eine Kette aus if/else-Blöcken. In einem einzigen match-Ausdruck lassen sich Einzelwerte, mehrere Werte (1 | 2) und Wertebereiche (100..199) kompakt und übersichtlich abdecken. Der default-Zweig behandelt alle nicht explizit genannten Fälle.
Der Vorteil gegenüber langen if-Ketten: Der Code bleibt flach statt verschachtelt, der Compiler kann warnen, wenn Fälle fehlen, und die Lesbarkeit steigt deutlich — besonders bei Zustandsautomaten, Protokoll-Parsern und Fehlerbehandlung.
match (value) {
case 0 => PrintStr("zero\n");
case 1 | 2 => PrintStr("small\n");
default => PrintStr("other\n");
}
Der Pipe-Operator |> leitet den Rückgabewert eines Ausdrucks als ersten Parameter an die nächste Funktion weiter. Das ersetzt tief verschachtelte Ausdrücke wie addOne(double(5)) durch eine lineare, von oben nach unten lesbare Abfolge von Transformationsschritten.
Das ist besonders nützlich bei Datenpipelines: Eingabewert aufbereiten, filtern, umrechnen, ausgeben — jeder Schritt ist eine eigene Zeile, die Reihenfolge ist sofort ersichtlich. Im Energy-Aware-Modell kann der Compiler solche Pipelines zusätzlich auf Instruktionsbasis optimieren.
→ Funktionen (vertieft) — Pipe-Operator, Higher-Order Functions und anonyme Funktionen
fn double(x: int64): int64 {
return x * 2;
}
fn addOne(x: int64): int64 {
return x + 1;
}
var result := 5
|> double()
|> addOne();
Lyx ist statisch und stark typisiert: Jeder Wert hat zur Compile-Zeit einen festen Typ, und implizite Konvertierungen zwischen Typen gibt es nicht. Wer einen Wert in einen anderen Typ umwandeln will, muss das explizit mit dem as-Operator tun. Das verhindert eine ganze Klasse von Fehlern, die in schwach typisierten Sprachen erst zur Laufzeit sichtbar werden.
Lyx bietet signierte und unsignierte Ganzzahlen in vier Breiten. Der Standard-Ganzzahltyp ist int64 — auf modernen 64-Bit-Architekturen (x86_64, ARM64, RISC-V) entspricht das der nativen Registergröße und verursacht keinen Overhead. Kleinere Typen (int8, uint16 etc.) werden verwendet, wenn Speicherplatz oder das genaue Bit-Layout eine Rolle spielen — zum Beispiel beim Zugriff auf Hardware-Register, beim Parsen von Netzwerkprotokollen oder beim Befüllen von Puffern.
Unsignierte Typen eignen sich überall dort, wo negative Werte konzeptionell ausgeschlossen sind: Array-Indizes, Bitmaps, Byte-Felder, Speicheradressen. Der Compiler kann für unsignierte Typen bestimmte Optimierungen aggressiver anwenden, weil kein Überlaufverhalten für negative Zahlen berücksichtigt werden muss.
Jeder Ganzzahltyp hat ein eigenes Literal-Suffix, sodass der Typ bereits im Quellcode erkennbar ist, ohne den Kontext lesen zu müssen.
| Typ | Bits | Vorzeichen | Typisches Einsatzgebiet |
|---|---|---|---|
int8 | 8 | signiert | Kompakte signierte Werte, Temperaturoffsets |
int16 | 16 | signiert | Audio-Samples (PCM), kleine Koordinaten |
int32 | 32 | signiert | Kompatibilität mit C-APIs, POSIX-Codes |
int64 | 64 | signiert | Standard — Zähler, Berechnungen, IDs |
int | 64 | signiert | Alias für int64 |
uint8 | 8 | unsigniert | Bytes, Protokollfelder, Pixel-Kanäle |
uint16 | 16 | unsigniert | Ports, Unicode-Codepoints, kleine Adressen |
uint32 | 32 | unsigniert | IPv4-Adressen, CRC-Werte, Flags |
uint64 | 64 | unsigniert | Bitmaps, Speicheradressen, große Zähler |
var temperature: int8 := -12i8;
var port: uint16 := 8080u16;
var checksum: uint32 := 0xDEADBEEFu32;
var counter: int64 := 1_000_000; // Unterstriche zur Lesbarkeit erlaubt
isize und usize passen sich der nativen Pointer-Größe der Zielarchitektur an — 64 Bit auf x86_64 und ARM64, 32 Bit auf ESP32 (Xtensa). Sie sind die richtige Wahl für Array-Indizes und Pointer-Arithmetik, weil sie garantieren, dass kein Wertebereich-Überlauf entstehen kann, der nur auf bestimmten Plattformen auftritt.
| Typ | Beschreibung | Verwendung |
|---|---|---|
isize | Vorzeichenbehaftete Pointer-Größe | Pointer-Differenzen, Offsets |
usize | Vorzeichenlose Pointer-Größe | Array-Indizes, Längen, Kapazitäten |
Lyx unterstützt zwei Fließkommatypen nach IEEE 754. f64 ist der Standard — er bietet ausreichend Präzision für nahezu alle Berechnungen und entspricht dem, was moderne FPUs nativ verarbeiten. f32 wird verwendet, wenn Speicherplatz oder Bandbreite knapp ist, zum Beispiel in großen SIMD-Arrays oder bei der Übertragung von Sensordaten über ressourcenbeschränkte Verbindungen.
| Typ | Bits | Präzision | Typisches Einsatzgebiet |
|---|---|---|---|
f32 | 32 | ~7 Dezimalstellen | SIMD-Berechnungen, Grafik, kompakte Puffer |
f64 | 64 | ~15 Dezimalstellen | Wissenschaftliche Berechnungen, Standard |
In sicherheitskritischen Systemen nach DO-178C müssen Fließkommaberechnungen besonders sorgfältig validiert werden, da Rundungsfehler akkumulieren können. Lyx bietet keine Fließkommaliterale ohne explizite Markierung — 3.14 ist immer f64, 3.14f32 ist f32.
var pi: f64 := 3.14159265358979;
var ratio: f32 := 0.5f32;
var altitude: f64 := sensor_read() as f64; // expliziter Cast
Lyx unterscheidet zwei Zeichentypen mit unterschiedlichem Abstraktionsniveau. char repräsentiert ein einzelnes ASCII-Zeichen (8 Bit). pchar ist ein nullterminierter Zeiger auf einen Zeichenpuffer — das direkte Äquivalent zu char* in C. Das macht FFI-Aufrufe und die Arbeit mit Systemschnittstellen reibungslos, weil keine automatische Konvertierung stattfindet.
Der höhere string-Typ (aus std.string) bietet dynamische Länge, UTF-8-Unterstützung und sichere Operationen auf Kosten eines kleinen Overhead. Für Systemprogrammierung und Protokoll-Implementierungen ist pchar oft die bessere Wahl, weil er direkt in Netzwerkpuffer oder Speicherbereiche zeigen kann, ohne Kopien zu erzeugen.
| Typ | Beschreibung | Verwendung |
|---|---|---|
char | Einzelnes ASCII-Zeichen | Zeichenverarbeitung, Protokoll-Bytes |
pchar | Nullterminierter C-String-Pointer | FFI, Systemaufrufe, Puffer-Referenzen |
string | Dynamischer High-Level-String | Textverarbeitung, User-Interfaces |
var initial: char := 'A';
var label: pchar := "Sensor-01"; // String-Literal ist pchar
var message: string := String("Hallo"); // std.string High-Level-Typ
bool nimmt genau zwei Werte an: true und false. Anders als in C gibt es keine implizite Konvertierung von Ganzzahlen zu bool — ein int64 ist kein bool, auch wenn sein Wert 0 oder 1 ist. Das verhindert einen häufigen Fehler, bei dem Rückgabewerte von Funktionen (die eigentlich Fehlercodes sind) versehentlich als Wahrheitswerte interpretiert werden.
var active: bool := true;
if (active) {
PrintStr("System läuft\n");
}
// Fehler: implizite Konvertierung nicht erlaubt
// if (getSensorValue()) { ... } // Kompilierungsfehler
// Richtig: expliziter Vergleich
if (getSensorValue() > 0) { ... }
Enums gruppieren zusammengehörige benannte Ganzzahl-Konstanten. Intern werden sie als int64 abgebildet, was FFI-Kompatibilität und direktes Logging ohne Konvertierung sicherstellt. Ausführliche Erklärung: Sprachüberblick → Enums.
enum Direction { North, South, East, West }
var heading: int64 := Direction::North;
Ein Tupel fasst mehrere Werte unterschiedlicher Typen zu einer temporären Einheit zusammen, ohne dafür einen benannten Struct definieren zu müssen. Haupteinsatzgebiet sind Mehrfach-Rückgaben aus Funktionen. Ausführliche Erklärung: Sprachüberblick → Tuple-Rückgaben.
fn minMax(a: int64, b: int64): (int64, int64) {
if (a < b) { return (a, b); }
return (b, a);
}
var lo, hi := minMax(42, 17);
Lyx kennt zwei Array-Formen. Dynamische Arrays (array<T>) sind Fat Pointer mit automatischer Kapazitätsverwaltung — sie wachsen bei Bedarf nach und speichern intern Länge und Kapazität. Statische Arrays (var buf: uint8[256]) haben eine feste, zur Compile-Zeit bekannte Größe und liegen vollständig auf dem Stack — kein Heap, keine Allokation, deterministisches Verhalten.
Für Embedded- und Safety-Code sind statische Arrays vorzuziehen, weil ihre Speichergröße zur Compile-Zeit prüfbar ist. Dynamische Arrays sind das Mittel der Wahl für Server- und Tool-Code, wo Flexibilität wichtiger ist.
parallel Array<T> ist eine SIMD-optimierte Variante: Der Compiler legt die Daten so im Speicher an, dass Vektoroperationen (SSE, AVX, NEON) ohne manuelle Intrinsics genutzt werden können.
| Typ | Speicher | Größe | Verwendung |
|---|---|---|---|
array<T> | Heap | Dynamisch | Allgemeine Listen, Puffer |
var buf: T[N] | Stack | Compile-Zeit-Konstant | Embedded, Safety-Critical |
parallel Array<T> | Heap (SIMD-aligned) | Dynamisch | Batch-Berechnungen, Signalverarbeitung |
var list: array<int64> := [1, 2, 3, 4, 5];
list.push(6); // wächst automatisch nach
var buf: uint8[256]; // statischer Stack-Puffer
buf[0] := 0xAA;
var signal := parallel Array<f32>(1024); // SIMD-optimiertes Array
Structs sind stackbasierte Value-Types (Copy-by-Value, keine Vererbung), Klassen sind heapbasierte Reference-Types mit Vererbung und virtuellen Methoden. Die bewusste Trennung macht die Performance-Implikationen im Code sichtbar. Ausführliche Erklärung: Sprachüberblick → Structs und Klassen.
Range-Typen sind eines der markantesten Features von Lyx für sicherheitskritische Software. Sie definieren einen Ganzzahltyp mit einem fest eingeschränkten Wertebereich. Der Compiler prüft konstante Zuweisungen zur Compile-Zeit und fügt für dynamische Zuweisungen automatisch Laufzeit-Checks ein.
Das Ziel ist, ungültige Systemzustände unmöglich zu machen — nicht durch nachträgliche Validierung, sondern durch das Typsystem selbst. Eine Variable vom Typ Altitude kann schlicht keinen Wert außerhalb von -1000 bis 60000 annehmen. Das ist eine zentrale Anforderung der DO-178C für Avionik-Software.
type Altitude = int64 range -1000..60000; // Meter über NN
type Speed = int64 range 0..300; // km/h
type Percent = int64 range 0..100;
var height: Altitude := 10500; // OK
var speed: Speed := 350; // Kompilierungsfehler: außerhalb des Bereichs
fn setThrottle(p: Percent) {
// p kann hier niemals < 0 oder > 100 sein — garantiert durch den Typ
}
qbool ist ein Typ, der keinen binären Wahrheitswert speichert, sondern eine Wahrscheinlichkeit zwischen 0.0 und 1.0. Das Literal dafür ist ein Gleitkommazahl mit dem Suffix q. Er wird im Energy-Aware-Modell eingesetzt: Bei niedrigen Energy-Levels (@energy(1)) kann der Compiler qbool-Entscheidungen probabilistisch abkürzen, statt teure exakte Vergleiche durchzuführen.
Praktisch ist qbool für heuristische Filter, unsichere Sensorfusionen oder KI-gestützte Entscheidungen, bei denen ein „wahrscheinlich wahr“ ausreicht und der Energieverbrauch eines exakten Ergebnisses nicht gerechtfertigt ist.
var confidence: qbool := 0.85q; // 85 % Wahrscheinlichkeit
if (confidence) {
// wird ausgeführt, wenn der Wert über dem internen Schwellenwert liegt
ProcessResult();
}
Lyx erlaubt keine impliziten Typkonvertierungen. Jede Umwandlung muss mit dem as-Operator explizit ausgedrückt werden. Das macht an jeder Stelle im Code sichtbar, dass eine Konvertierung stattfindet — und ob dabei Präzision verloren gehen kann.
var x: int64 := 42;
var y: f64 := x as f64; // int64 → f64, verlustfrei
var z: int32 := x as int32; // int64 → int32, möglicher Wertebereichsverlust
var pi: f64 := 3.14;
var n: int64 := pi as int64; // f64 → int64: Nachkommastellen werden abgeschnitten
void ist der Rückgabetyp für Funktionen, die keinen Wert zurückgeben. Er ist kein echter Datentyp und kann nicht als Variablentyp verwendet werden.
fn resetCounter(): void {
counter := 0;
}
Das Modulsystem von Lyx basiert auf dem Konzept der Units: Jede Quelldatei ist eine eigenständige Einheit mit einem expliziten Namen, einer definierten öffentlichen Schnittstelle und klaren Abhängigkeiten. Keine impliziten Includes, keine globalen Header — jede Abhängigkeit ist im Quellcode deklariert und für den Compiler nachvollziehbar.
Jede Lyx-Quelldatei beginnt mit einer unit-Deklaration. Sie legt den Namen der Unit fest, unter dem sie von anderen Dateien importiert werden kann. Ohne diese Deklaration ist die Datei für das Modulsystem nicht sichtbar.
Die Deklaration hat keinen Einfluss auf den Dateinamen — sie ist eine logische Zuordnung, die der Compiler für die Abhängigkeitsauflösung und für Fehlermeldungen verwendet.
unit MathHelpers;
// Ab hier folgen Typen, Konstanten und Funktionen dieser Unit.
Standardmäßig ist alles, was in einer Unit definiert wird, nur innerhalb dieser Unit sichtbar. Wer eine Funktion, einen Typ oder eine Konstante nach außen exportieren will, markiert sie explizit mit pub.
Das ist eine bewusste Entscheidung gegen das C-Modell, bei dem alles in einem Header standardmäßig öffentlich ist. In Lyx ist die öffentliche Schnittstelle einer Unit das, was mit pub markiert ist — nicht mehr und nicht weniger. Das erzwingt saubere Kapselung und macht die API einer Unit auf einen Blick erkennbar.
unit MathHelpers;
pub fn square(x: int64): int64 {
return x * x;
}
fn internalHelper(x: int64): int64 { // nur intern sichtbar
return x * 2;
}
Eine Unit wird über ihren vollständig qualifizierten Namen importiert. Der Compiler sucht die entsprechende Datei anhand dieses Namens im konfigurierten Suchpfad. Nach dem Import stehen alle pub-Symbole der Unit direkt zur Verfügung.
import std.math;
import std.string;
import std.net.http;
Statt einzelner Units kann auch ein ganzer Namespace auf einmal importiert werden. Das ist sinnvoll, wenn mehrere Units eines Pakets gemeinsam verwendet werden — zum Beispiel alle Audio-Untermodule oder alle Netzwerkprotokolle eines Projekts.
Der Wildcard-Import zieht alle Units des Namespace in den aktuellen Scope. Er sollte mit Bedacht eingesetzt werden: In großen Projekten kann er die Abhängigkeitsgraphen unübersichtlich machen, in kleineren Scripts oder Prototypen ist er sehr praktisch.
import std.audio.*; // lädt std.audio, std.audio.alsa, std.audio.mpg123, std.audio.playback
import std.net.*; // lädt alle 21 Net-Units auf einmal
import std.validate.*; // lädt EAN, IBAN, ISBN, Luhn, VAT
Units lassen sich mit dem Compiler in ein binäres Zwischenformat übersetzen. Diese vorkompilierten Units tragen die Dateiendung .lyu und enthalten bereits das interne Lyx-IR — der Compiler muss sie beim nächsten Build nicht mehr parsen und analysieren, sondern kann sie direkt einbinden.
Das beschleunigt die Kompilierung großer Projekte erheblich, weil stabile Dependencies (eigene Bibliotheken, externe Pakete) nur einmal kompiliert werden müssen. Beim Import prüft der Compiler zuerst, ob eine .lyu-Version der angeforderten Unit existiert. Findet er sie, wird diese bevorzugt geladen. Nur wenn keine .lyu-Datei vorhanden ist, fällt er auf die .lyx-Quelldatei zurück.
Unit erzeugen:
lyxc std/math.lyx --compile-unit -o std/math.lyu
Beim nächsten Build wird math.lyu automatisch bevorzugt:
import std.math; // lädt math.lyu, falls vorhanden — sonst math.lyx
Metadaten einer vorkompilierten Unit anzeigen:
lyxc --unit-info std/math.lyu
Der Compiler sucht Units standardmäßig im aktuellen Verzeichnis und in den Standardpfaden der Standardbibliothek. Eigene Bibliotheken oder Drittanbieter-Units werden über den -I-Flag in den Suchpfad aufgenommen. Mehrere Pfade sind möglich.
lyxc app.lyx -I ./libs -I ./vendor -I /opt/lyx/extra
Mit –trace-imports gibt der Compiler während der Kompilierung aus, welche Units er in welcher Reihenfolge lädt und ob er dabei auf eine .lyx- oder .lyu-Version zurückgreift. Das ist nützlich beim Debugging von Abhängigkeitsproblemen oder beim Aufspüren unnötiger Imports.
lyxc app.lyx --trace-imports
Die Lyx Standard Library umfasst über 84 dokumentierte Units in 17 Kategorien — von Speicherverwaltung und Betriebssystem-Abstraktion über Kryptographie und Netzwerkprotokolle bis hin zu Machine Learning und Audioverarbeitung.
Das zentrale Designprinzip ist Zero External Dependencies: Alle Units sind in nativem Lyx implementiert und brauchen keine externen C-Bibliotheken oder Treiber. Datenbankclients implementieren die Protokolle direkt über TCP. Netzwerk-Units sprechen die Protokolle nativ, ohne auf libcurl, OpenSSL oder ähnliche Abhängigkeiten zu setzen. Das hält Deployments minimal, Builds deterministisch und ermöglicht Cross-Compilation auf Embedded-Targets wie ESP32 oder RISC-V, wo externe Bibliotheken oft nicht verfügbar sind.
Der Kern der Standardbibliothek ist bewusst klein gehalten. std.system liefert grundlegende Systemkonstanten und Plattform-Identifikation. std.alloc stellt Heap-Allokation mit POSIX-Alignment bereit — die Basis für alle dynamischen Datenstrukturen. std.error und std.result bieten typsichere Fehlerbehandlung ohne Exceptions: Funktionen geben Ergebnis-Typen zurück, die entweder einen Wert oder einen Fehlercode enthalten, ähnlich wie Rust's Result<T, E>. std.qbool implementiert den probabilistischen Wahrheitswert für das Energy-Aware-Programmiermodell.
std.io ist die grundlegende I/O-Unit für Textausgabe und Lesen von Standardein-/ausgabe. std.crt und std.crt_raw bieten Terminal-Steuerung: Cursor-Positionierung, Farben, Zeichensätze — die Basis für LyxVision-Anwendungen. std.log implementiert strukturiertes Logging mit Levels (Debug, Info, Warning, Error) und konfigurierbarem Output. std.buffer stellt Byte-Puffer mit sicherem Lesen und Schreiben bereit. std.pack ermöglicht binäres Serialisieren und Deserialisieren nach definierten Layouts — essenziell für Protokoll-Implementierungen und Dateiformate.
Diese Gruppe abstrahiert direkte Betriebssystem-Aufrufe. std.fs deckt Datei- und Verzeichnisoperationen ab. std.os gibt Zugriff auf Umgebungsvariablen, Signale und POSIX-Systemaufrufe. std.env liest Kommandozeilenargumente aus dem argv-Array. std.process startet und verwaltet Kindprozesse. std.thread implementiert POSIX-Threads mit Mutex, Condition Variables, Thread-Local Storage und atomaren Operationen. std.time liefert Echtzeit-Uhren, monotone Zeitgeber und Zeitzonenunterstützung. std.systeminfo fragt Hardware-Parameter ab: CPU-Kerne, Speichergröße, Plattformname.
→ Threads & Nebenläufigkeit – vollständige Dokumentation (POSIX / Linux / macOS)
→ Nebenläufigkeit auf Embedded-Targets & RTOS (Bare-Metal, ARM Cortex-M, RISC-V, ESP32/FreeRTOS)
std.string ist der High-Level-String-Typ mit dynamischer Länge, einem StringBuilder-Muster und UTF-8-Unterstützung. std.conv konvertiert zwischen Ganzzahlen, Fließkommazahlen und Strings in beide Richtungen, inklusive Hex- und Binärdarstellung. std.regex bietet reguläre Ausdrücke mit einer einfachen und einer kompilierten API für Performance-kritische Anwendungen.
Für strukturierte Datenformate gibt es std.json, std.xml, std.yaml und std.ini — alle mit Parser und Serialisierer. std.url parst und baut URLs nach RFC 3986. std.html generiert HTML und escaped Sonderzeichen. std.base64 kodiert und dekodiert nach RFC 4648.
std.list bietet dynamische Listen, statische Listen fixer Größe (8 und 16 Elemente), einen LIFO-Stack und eine FIFO-Queue — alle typisiert auf int64. std.vector implementiert zweidimensionale Vektoren (Vec2) mit vollständiger Vektor-Arithmetik; std.vector_batch verarbeitet Arrays von Vektoren SIMD-optimiert. std.sort stellt Sortieralgorithmen für Integer-Arrays bereit. std.hash ist die umfangreichste Unit der Standardbibliothek: Sie enthält über 20 Hash-Algorithmen — von schnellen nicht-kryptographischen Hashes (FNV-1a, DJB2, Murmur3, CRC32, xxHash, CityHash, FarmHash) über kryptographische Hashes (MD5, SHA-256, SHA-3, BLAKE3) bis zu Passwort-Hashing-Funktionen (bcrypt, Argon2, scrypt, PBKDF2).
std.math deckt trigonometrische, logarithmische und exponentielle Funktionen ab. std.math.constants stellt mathematische Konstanten wie π und e bereit. std.math_batch verarbeitet Arrays von Zahlen in einem einzigen Funktionsaufruf — SIMD-optimiert für Signalverarbeitung und numerische Simulation. std.stats berechnet deskriptive Statistiken (Mittelwert, Median, Varianz, Standardabweichung, Quantile, Korrelation). std.stats_batch tut dasselbe für große Datensätze mit Batch-Verarbeitung. std.units rechnet physikalische Einheiten um (Meter ↔ Fuß, Kelvin ↔ Celsius, Grad ↔ Radiant).
std.ml implementiert klassische ML-Algorithmen nativ: lineare und logistische Regression, k-Nearest Neighbors, Naive Bayes, Decision Trees, K-Means-Clustering — alles ohne externe Abhängigkeiten. std.fasttext ist eine native Lyx-Implementierung der FastText-Wortvektor-Engine (angelehnt an Facebook Research, 2016): Sie trainiert Skip-gram- und CBOW-Modelle per Stochastic Gradient Descent, erzeugt dichte Einbettungsvektoren (Standard: 100 Dimensionen) und findet semantisch ähnliche Wörter, löst Analogien und klassifiziert Texte.
Der std.net-Namespace ist der umfangreichste der Standardbibliothek mit 21 Units. Die Schichtung ist klar: std.net.syscalls und std.net.types bilden das Fundament, std.net.socket und std.net.tls die Transport-Schicht, und darüber liegen die Anwendungsprotokolle.
Alle Protokolle sind direkte Eigenimplementierungen — kein Wrapping von libcurl, libssh oder OpenSSL:
| Protokoll | Unit | Besonderheit |
|---|---|---|
| TCP/UDP/Raw | std.net.socket | 9 Socket-Typen inkl. ICMP, ARP, Unix Domain |
| TLS 1.2/1.3 | std.net.tls | Natives TLS ohne OpenSSL |
| DNS | std.net.dns | Vollständiger Resolver inkl. DNSSEC-Vorbereitung |
| HTTP/HTTPS | std.net.http, std.net.https | Client-Implementierungen |
| SMTP / IMAP | std.net.smtp, std.net.imap | E-Mail senden und empfangen |
| SSH | std.net.ssh | Verbindung, Authentifizierung, Tunneling |
| MQTT | std.net.mqtt | IoT-Messaging-Protokoll |
| QUIC | std.net.quic | UDP-basiertes Transportprotokoll |
| SIP | std.net.sip | VoIP-Signalisierung |
| LDAP | std.net.ldap | Verzeichnisdienste, Active Directory |
| SNMP | std.net.snmp | Netzwerkgeräte-Monitoring |
| BGP | std.net.bgp | Border Gateway Protocol |
| WHOIS | std.net.whois | Domain-Abfragen |
| NTP | std.net.ntp | Zeitsynchronisation |
| Telnet | std.net.telnet | Legacy-Terminal-Protokoll |
| MongoDB | std.net.mongo | Wire Protocol direkt über TCP |
| ASN.1 | std.net.asn1 | Basis-Kodierung für LDAP und TLS |
std.db.mysql implementiert das MySQL-Binärprotokoll (Protocol 41) vollständig über TCP — ohne externe Clientbibliothek. Es unterstützt Verbindungsaufbau mit Authentifizierung, SQL-Abfragen, strukturierte Ergebnisverarbeitung, Transaktionen und typsichere Prepared Statements. std.db.redis implementiert das RESP-Protokoll von Redis direkt. Beide Units brauchen keine installierten Datenbanktreiber, was sie besonders für Embedded-Server und minimale Deployments geeignet macht.
std.crypto.aes implementiert AES-128 und AES-256 mit ECB- und CBC-Modus. std.crypto.sha1 berechnet SHA-1-Hashes — primär für Protokoll-Kompatibilität (z.B. MySQL-Authentifizierung). Für moderne Kryptographie-Anforderungen stehen in std.hash SHA-256, SHA-3, BLAKE3 und die Passwort-Hashing-Funktionen bereit.
std.audio ist die Basis-Unit mit Typen und Parsern für WAV- und MP3-Dateien sowie PCM-Konvertierungsfunktionen (8-Bit → 16-Bit, Stereo → Mono). std.audio.alsa bindet ALSA für Linux-Soundausgabe an. std.audio.mpg123 nutzt libmpg123 für MP3-Dekodierung. std.audio.playback koordiniert die Wiedergabe-Pipeline.
std.geo implementiert geografische Berechnungen im WGS-84-System mit Mikro-Grad-Auflösung (1 Grad = 1.000.000 Einheiten): Haversine-Distanz, Peilwinkel, Bounding-Boxes, Punkt-in-Rechteck. std.rect, std.circle und std.color decken 2D-Geometrie und Farbwerte ab.
std.validate enthält Prüfroutinen für EAN-Barcodes, IBAN-Bankverbindungen, ISBN-Buchnummern, Luhn-Prüfziffern und europäische Umsatzsteuer-IDs (VAT) — nützlich für E-Commerce- und Finanzanwendungen.
→ Die vollständige Referenz aller Units mit Typen, Konstanten und Funktionssignaturen: Standardbibliothek – Vollständige Übersicht
Neben dem std/-Namespace gibt es einen eigenständigen data/-Namespace mit einer Pandas-ähnlichen Datenanalyse-Bibliothek. Sie ist speziell auf tabellarische Datenverarbeitung, statistische Auswertungen und ETL-Pipelines ausgelegt und folgt dem gleichen Zero-Dependency-Prinzip wie die Standardbibliothek.
| Unit | Beschreibung |
|---|---|
data.core | Kern-Datenstrukturen: Series, DataFrame, Statistik, GroupBy, Joins, boolesche Indizierung, Rolling Window, Normalisierung |
data.io | CSV-Import (ReadCSV) und -Export (WriteCSV) |
data.stats_batch | SIMD-bereite Batch-Statistikfunktionen für 4, 8 und 16 Werte direkt als Parameter |
Der zentrale Typ ist DataFrame — eine zweidimensionale Tabelle mit bis zu 16 benannten Spalten und 1024 Zeilen, typisiert über COL_INT64, COL_FLOAT, COL_STRING und COL_BOOL. Series ist die eindimensionale Entsprechung. Beide unterstützen Aggregationen (Summe, Mittelwert, Median, Varianz, Korrelation), Filterung mit Vergleichsoperatoren und booleschen Masken, GroupBy, Joins (Inner/Left/Right/Outer), Pivot, Melt sowie Transformationen wie Rolling Mean, kumulative Summe und Z-Score-Normalisierung.
import data.core;
import data.io;
var df: DataFrame := ReadCSV("sales.csv");
// Zeilen filtern: score > 80
var high: DataFrame := DataFrameFilter(df, "score", OP_GT, 80);
// Statistik auf einer Spalte
var s: Series := DataFrameGetSeries(df, "revenue");
PrintInt(SeriesMean(s));
// Gruppieren und summieren
var gb: GroupByResult := DataFrameGroupBy(df, "region");
var totals: DataFrame := GroupBySum(gb, "revenue");
DataFramePrint(totals);
→ Vollständige Referenz mit allen Typen, Konstanten und Funktionen: Daten-Bibliothek – Vollständige Übersicht
LyxVision ist das integrierte Text-UI-Framework für terminalbasierte Anwendungen.
Verfügbare Komponenten:
Beispiel:
import lyxvision.app;
import lyxvision.window;
Für grafische Desktop-Anwendungen stehen Qt5-Bindings zur Verfügung:
lyxc program.lyx -o program
lyxc app.lyx --target=win64
lyxc app.lyx --target=linux
lyxc app.lyx --target=arm64
lyxc app.lyx --target=macos-arm64
lyxc app.lyx --target=esp32
lyxc app.lyx --target=riscv
--lint
--lint-only
--static-analysis
--call-graph
--emit-asm
--asm-listing
--dump-relocs
--trace-imports
--ast-dump
--symtab-dump
--trace-passes
--ir-source-map
--type-reasoning
--constraint-log
--provenance
--runtime-checks
--profile
--trace
Vorkompilierte Units beschleunigen große Projekte.
Unit erzeugen:
lyxc math.lyx --compile-unit -o math.lyu
Informationen anzeigen:
lyxc --unit-info math.lyu
Debugsymbole einbetten:
lyxc math.lyx --compile-unit --debug-symbols -o math.lyu
Lyx ist von Grund auf für den Einsatz in sicherheitskritischen Systemen ausgelegt. Die Sprache und ihr Compiler erfüllen die Anforderungen der DO-178C (Software Considerations in Airborne Systems and Equipment Certification) für die Entwicklungssicherheitsstufen DAL-A (Katastrophal) bis DAL-E (Kein Sicherheitseffekt).
Zentrale Bausteine sind dedizierte Safety-Pragmas (@flight_crit, @redundant für Triple Modular Redundancy, @stack_limit, @wcet, @integrity, @volatile, @packed, @dal), Range-Types mit automatischer Bereichsprüfung zur Compile-Zeit und zur Laufzeit, sowie eine vollständige Analyse-Toolchain mit MC/DC-Instrumentierung, statischer Analyse, Call-Graph-Erzeugung, Stack-Check und Provenance Tracking für auditierbare, reproduzierbare Builds.
→ Aerospace & Safety – Vollständige Dokumentation
→ DO-178C Compliance — DAL-Stufen, MC/DC, WCET, TMR, Memory Scrubbing
| Plattform | Architektur |
|---|---|
| Linux | x86_64 |
| Windows | x86_64 |
| macOS Intel | x86_64 |
| macOS Apple Silicon | ARM64 |
| Linux ARM64 | ARM64 |
| ESP32 | Xtensa |
| RISC-V | RV64 |
Aktuelle Version: v0.8.5-aerospace
Lyx verbindet moderne Sprachfeatures, native Multi-Plattform-Kompilierung, umfangreiche Standardbibliotheken und professionelle Analysewerkzeuge in einer einheitlichen Toolchain für Embedded-, System-, Server- und Aerospace-Software.