Inhaltsverzeichnis

Lyx – Datentypen

Stand: V1.0.21A

 
Globale Arrays und Structs tragen seit lyxc 1.0.17C (#1256, #1299). Auf Modulebene deklarierte Aggregate verhalten sich jetzt wie lokale: var q: [8]int64; nimmt einen schreibenden Indexzugriff an, ein Initialisierer (:= [1,2,3,4,5,6,7,8]) bleibt erhalten, und ein Feldzugriff auf einen globalen Struct (s.x := 3) wirkt.

Bis 1.0.16x stürzte der schreibende Zugriff ab, der Initialisierer wurde verworfen, und Struct-Felder blieben still auf 0.

Das Typsystem von Lyx ist explizit und statisch: Jede Variable hat einen festen Typ, der zur Compile-Zeit bekannt ist. Implizite Konvertierungen gibt es nicht – alle Typumwandlungen müssen mit dem as-Operator explizit geschrieben werden. Dadurch werden typische Fehlerquellen (truncation, sign-confusion, float-to-int-Überraschungen) bereits beim Compilieren erkannt.

1. Ganzzahl-Typen (signiert)

Alle signierten Ganzzahl-Typen verwenden Zweierkomplement-Darstellung. Integer-Arithmetik wraps bei Überlauf (modular arithmetic); für explizite Überlauf-Erkennung stehen die Safe-Varianten aus std.result zur Verfügung.

Typ Kurzform Bits Wertebereich Literal Typisches Einsatzgebiet
int8 i8 8 −128 … 127 var x: int8 := -100; Kompakte Felder, Protokoll-Bytes mit Vorzeichen
int16 i16 16 −32 768 … 32 767 var x: int16 := 4200; Audio-Samples, kompakte Sensordaten
int32 i32 32 −2 147 483 648 … 2 147 483 647 var x: int32 := 2_147_483_647; Koordinaten, API-Rückgabewerte
int64 i64 64 −9 223 372 036 854 775 808 … 9 223 372 036 854 775 807 var x: int64 := 42; Standard-Ganzzahl in Lyx

Jeder Typ hat genau eine Kurzform; beide Schreibweisen bezeichnen denselben Typ und sind überall gleichwertig — als Variablentyp, Feldtyp, Parameter und im as-Cast. Kanonisch ist laut ebnf.md §7 die Kurzform, im Bestand überwiegt bei den signierten Typen die Langform.

 
Ein blankes int gibt es nicht — ebenso wenig wie uint. Es existieren nur die vier breitenbehafteten Typen oben mit ihren Kurzformen; var x: int := 42; meldet unknown type in var decl 'int'. Wer die Standard-Ganzzahl meint, schreibt int64 (oder i64).

var a: int64 := 9_000_000_000;   // Unterstriche als Tausender-Trenner
var b: int8  := -100;
var c: int32 := 2_147_483_647;

// Überlauf-sicheres Addieren (std.result)
import std.result;
var result := SafeAdd(a, 1);
if (ResultInt64IsOk(result)) {
    PrintLn(IntToStr(Unwrap(result)));
} else {
    Print("Überlauf erkannt");
}

2. Ganzzahl-Typen (unsigniert)

Unsignierte Typen sind ideal für Bit-Operationen, Hardware-Register (MMIO), Speichergrößen und Protokoll-Felder, die keine negativen Werte kennen.

Typ Kurzform Bits Wertebereich Literal Typisches Einsatzgebiet
uint8 u8 8 0 … 255 var b: uint8 := 255; Bytes, MMIO-Register, Protokoll-Rohdaten
uint16 u16 16 0 … 65 535 var p: uint16 := 8080; Ports, Checksummen, kompakte Flags
uint32 u32 32 0 … 4 294 967 295 var f: uint32 := 0xFF; IPv4-Adressen, CRC32, Hardware-Register
uint64 u64 64 0 … 18 446 744 073 709 551 615 var h: uint64 := 0; Datei-Offsets, Hash-Werte, 64-Bit-Masken

Auch hier hat jeder Typ genau eine Kurzform. Die kurze Schreibweise ist bei den unsignierten Typen im Bestand die häufigere (var b: u8 := 255;); ein blankes uint gibt es nicht.

// Bit-Manipulation (typischer uint32-Einsatz)
var flags: uint32 := 0x00;
con FLAG_READ:  uint32 := 0x01;
con FLAG_WRITE: uint32 := 0x02;
con FLAG_EXEC:  uint32 := 0x04;

flags := flags | FLAG_READ | FLAG_WRITE;    // Bits setzen
var canRead  := flags & FLAG_READ  != 0; // Bit testen
flags := flags & ~FLAG_WRITE;               // Bit löschen

// Hex-Literale für MMIO — Adressen sind int64
var GPIO_BASE: int64 := 0x3FF44000;

3. Plattform-abhängige Typen

isize und usize sind zwar reservierte Wörter, werden vom Compiler aber nicht unterstützt — eine Deklaration scheitert mit unknown type in var decl. Für Indizes, Längen und Offsets int64 bzw. uint64 verwenden; beide sind auf allen unterstützten Zielplattformen 64 Bit breit.

var len: int64 := 1024;
for i := 0 to len - 1 do {
    // ...
}

4. Fließkomma-Typen

Beide Typen folgen IEEE 754. Fließkomma-Literale ohne Suffix haben den Typ f64.

Typ Bits Präzision Wertebereich (ca.) Literal
f32 32 ~7 Dezimalstellen ±3.4 × 10³⁸ 3.14
f64 64 ~15 Dezimalstellen ±1.8 × 10³⁰⁸ 3.14

var pi:    f64 := 3.14159265358979;
var small: f32 := 1.5;
var zero:  f64 := 0.0;

// Expliziter Cast nötig bei gemischten Typen
var mixed: f64 := (small as f64) + pi;

// Wissenschaftliche Notation
var nano:  f64 := 1.0e-9;
var giga:  f64 := 1.0e9;

Literale: Reichweite und Genauigkeit

Ein Dezimalliteral wird korrekt gerundet in das nächstgelegene f64 übersetzt — über den ganzen Bereich, subnormale Werte eingeschlossen. Zwei Abweichungen sind seit lyxc 1.1.3B behoben:

Literal bis 1.1.2H seit 1.1.3B Issue
1.0e300 2 ULP daneben bitgenau #1657
1.0e308 1 ULP daneben bitgenau #1657
1.0e-320 (subnormal) still 0.0 2024 (Bitmuster) #1662
4.9e-324 (kleinstes subnormales) still 0.0 1 (Bitmuster) #1662

Nachgemessen über das Bitmuster, Referenzwerte aus CPython (struct.pack):

import std.io;
import std.alloc;

fn bits(d: f64): int64 { var c: int64 := alloc(8); pokef64(c, d); return peek64(c); }

fn main(): int64 {
    PrintLn(IntToStr(bits(1.0e300)));    // 9094988921128908188
    PrintLn(IntToStr(bits(1.0e-320)));   // 2024
    return 0;
}

Besondere IEEE-754-Werte

Wert Entsteht durch Verhalten
NaN (Not a Number) 0.0 / 0.0, Sqrt(-1.0) Jeder Vergleich mit NaN ergibt false
+Inf 1.0 / 0.0 Größer als jede endliche Zahl
-Inf -1.0 / 0.0 Kleiner als jede endliche Zahl
 
Safety-Hinweis:
In @flight_crit-Modulen aktiviert der Compiler FPU-Traps für NaN und Inf. Jede entstehende NaN/Inf-Operation löst einen panic aus, statt still einen ungültigen Wert weiterzupropagieren.

5. Wahrheitswerte – bool

bool nimmt genau die Werte true und false an. Im Speicher belegt er 1 Byte (Wert 0 oder 1). In der ABI wird er wie ein Integer-Register behandelt.

var ready: bool := false;
var done:  bool := true;

// Logische Operatoren
var both := ready & done;    // AND
var any  := ready | done;    // OR
var inv  := !ready;          // NOT

// Vergleiche ergeben bool
var big := (42 > 10);        // true
var eq  := (3 == 3);         // true  (== ist Vergleich, := ist Zuweisung!)

6. Probabilistische Wahrheitswerte – std.qbool

Probabilistische Logik ist in Lyx keine Spracherweiterung, sondern eine Bibliothek. Es gibt weder einen eingebauten Typ qbool noch Literale der Form 0.5q — beides ist ein Syntaxfehler. Stattdessen stellt die Unit std.qbool den Struct-Typ QBool bereit, der intern eine Wahrscheinlichkeit als f64 zwischen 0.0 (sicher falsch) und 1.0 (sicher wahr) hält.

import std.qbool;

var smoke: QBool := QBoolNew(0.8);   // Rauchsensor: 80 % sicher
var heat:  QBool := QBoolNew(0.9);   // Temperatursensor: 90 % sicher

var alarm := QBoolAnd(smoke, heat);  // kombiniert
var alert := Observe(alarm);         // bool — stochastische Auswertung

var neg    := QBoolNot(smoke);
var either := QBoolOr(smoke, heat);
var xor    := QBoolXor(smoke, heat);

Funktion Bedeutung
QBoolNew(prob: f64): QBool Wert anlegen; wird auf 0.0…1.0 begrenzt
Maybe(prob: f64): QBool Kurzform für QBoolNew
QBoolTrue() / QBoolFalse() Sichere Werte (1.0 / 0.0)
QBoolAnd/Or/Not/Xor Verknüpfungen, liefern wieder QBool
Observe(q): bool Stochastische Auswertung zu einem echten bool
GetProbability(q): f64 Rohwert auslesen
QBoolIsTrue/IsFalse/IsDeterministic Prüfungen ohne Zufall
QBoolEqual/Greater/Less Vergleiche

Da QBool ein gewöhnlicher Struct ist, gilt für ihn das aus Abschnitt 11: Zuweisung und Parameterübergabe kopieren den Inhalt; nur ref am Parameter greift auf das Original durch.

→ Details: Energy-Aware Programmiermodell – QBool

7. Zeichen und Strings

char – Ein Zeichen

char speichert ein einzelnes ASCII-Zeichen (1 Byte, unsigniert).

var c: char := 'A';
var nl: char := '\n';
var tab: char := '\t';

// ASCII-Wert als Zahl
var code: int64 := c as int64;   // 65

pchar – Nullterminierter String

pchar ist ein Alias für ^uint8 (C-kompatibles char*). String-Literale haben immer den Typ pchar und enden implizit mit \0.

var greeting: pchar := "Hallo Lyx\n";
var empty:    pchar := "";
let version:  pchar := "v0.9.0";

// An Funktionen übergeben
Print(greeting);

// pchar ist ein Pointer – kein eingebautes bounds-checking
// Sicherer String-Zugriff: std.string nutzen
import std.string;
var len := StrLen(greeting);   // 10

Situation Empfehlung
Ausgabe, FFI-Grenze pchar-Literal direkt übergeben
Zeichen lesen / suchen std.string.CharAt, std.string.StrFind
String manipulieren std.string.StringBuilder
Ownership-Übergabe Kommentar: wer ist für free verantwortlich

8. Adressen

Einen Zeigertyp gibt es in Lyx nicht: keine Schreibweise ^T, keinen Address-of-Operator und keinen Dereferenz-Operator. Eine Adresse ist ein gewöhnlicher int64; gelesen und geschrieben wird mit den eingebauten Primitiven.

Notation Bedeutung
int64 Eine Adresse — es gibt keinen eigenen Zeigertyp
peek8/16/32/64(a) Wert an Adresse a lesen
poke8/16/32/64(a, v) Wert v an Adresse a schreiben
null Entspricht der Adresse 0
T? Nullable Typ (darf null sein)

import std.alloc;

var zelle: int64 := alloc(8);
poke64(zelle, 42);
PrintLn(IntToStr(peek64(zelle)));   // 42
free(zelle, 8);

Die Adresse einer lokalen Variablen lässt sich nicht nehmen — wo andere Sprachen einen Ausgabeparameter per Zeiger übergeben, reicht Lyx eine mit alloc (Rohspeicher) angelegte Zelle durch.

→ Details: Low-Level: Speicherzugriff & Pragmas

9. Array-Typen

Lyx unterscheidet zwei Array-Formen mit unterschiedlichem Speicherort und Größenverhalten.

[N]T – Festes Array (Stack)

Größe muss eine Compile-Zeit-Konstante sein. Das Array lebt auf dem Stack oder im Data-Segment. Kein Heap-Zugriff.

var temps:   [7]f64;               // 7 Elemente, auf dem Stack
var buf:     [1024]uint8;          // 1 KiB Byte-Buffer
con SIZE := 64;
var data:    [SIZE]int64;          // Konstante als Größe erlaubt

// Zugriff
temps[0] := 20.5;
temps[6] := 19.8;

// Länge über len()
for i := 0 to len(temps) - 1 do {
    PrintFloat(temps[i]);
}

Feste Arrays sind Fat Pointer: Neben dem Daten-Pointer speichert Lyx intern Länge (len) und Kapazität (cap) mit. Das ergibt 24 Byte pro Fat-Pointer-Deskriptor.

Arrays von Structs

Ein [N]Struct trägt Zeiger-Slots: jedes Element ist ein eigenes Objekt, das beim Anlegen des Arrays erzeugt wird. Drei Folgen davon waren bis lyxc 1.1.1C fehlerhaft und sind jetzt behoben:

type P = struct { x: int64; y: int64; };

type Halter = class {
    items: [3]P;
    fn Create(): void { }
    pub fn Setze(i: int64, a: int64, b: int64): void {
        var s: P;
        s.x := a; s.y := b;
        self.items[i] := s;              // kopiert den Inhalt
    }
    pub fn Zeig(i: int64): void {
        PrintLn(StrConcat(StrConcat(IntToStr(self.items[i].x), " "), IntToStr(self.items[i].y)));
    }
};

fn Summe(a: [3]P): int64 {               // Array von Structs als Parameter
    return a[0].x + a[1].x + a[2].x;
}

fn main(): int64 {
    var h: Halter := new Halter();
    h.Setze(0, 1, 2); h.Setze(1, 5, 6);
    h.Zeig(0); h.Zeig(1);

    var arr: [3]P;
    arr[0].x := 10; arr[1].x := 20; arr[2].x := 30;
    PrintLn(StrConcat("Summe ueber Parameter: ", IntToStr(Summe(arr))));
    return 0;
}

1 2
5 6
Summe ueber Parameter: 60

array<T> – Dynamisches Array (Heap)

Größe wächst zur Laufzeit. Lebt auf dem Heap; muss explizit freigegeben werden.

var list: array<int64> := [1, 2, 3, 4, 5];
list.push(6);
list.push(7);
PrintLn(IntToStr(list.len()));  // 7
PrintLn(IntToStr(list[0]));  // 1

 
In @flight_crit- und @dal(A)-Modulen ist array&lt;T&gt; verboten (Heap-Allokation). Dort ausschließlich [N]T-Arrays auf dem Stack verwenden.

parallel Array<T> – SIMD-optimiert

Für SIMD-Verarbeitung (AVX2, NEON) existiert eine spezielle Array-Form mit garantierter Ausrichtung:

var samples: parallel Array<f32>(1024);   // SIMD-ausgerichtetes Array

@parallel
for i := 0 to 1023 do {
    samples[i] := samples[i] * 2.0;    // 8 Elemente pro AVX2-Instruktion
}

Typ Speicherort Größe Bounds-Check Safety-Code
[N]T Stack / Data Compile-Zeit-Konstant ✅ Ja ✅ Erlaubt
array&lt;T&gt; Heap Dynamisch ✅ Ja ❌ Verboten
parallel Array&lt;T&gt; Heap (SIMD-aligned) Dynamisch ✅ Ja ❌ Verboten

10. Map<K, V> – Assoziatives Array

Map&lt;K, V&gt; ist eine Hash-Map mit ganzzahligem Schlüssel und typisiertem Wert. Literale werden mit {key: value}-Syntax geschrieben.

var scores: Map<int64, int64> := {
    1: 95,
    2: 87,
    3: 92
};

scores[4] := 78;               // Einfügen / Überschreiben
var val := scores[1];          // 95
var has := 5 in scores;        // false
PrintLn(IntToStr(len(scores)));   // 4

Map&lt;pchar, V&gt; wird abgewiesen — Details und Ausweg unter Arrays & Maps. delete und for key, val in m sind nicht umgesetzt.

11. Verbundtypen

struct – Referenztyp (Heap)

Jede Struct-Variable ist ein Heap-Objekt; die Variable selbst hält einen Zeiger darauf. Seit 1.0.20F sind Structs durchgängig Werte: Zuweisung und Parameterübergabe kopieren beide den Inhalt. Wer das Original des Aufrufers ändern will, sagt das ausdrücklich mit ref (#1351, #1528).

type Inner = struct { a: int64; }
type Outer = struct { id: int64; inner: Inner; }

fn AlsWert(o: Outer): void      { o.id := 555; o.inner.a := 555; }
fn AlsRef(ref o: Outer): void   { o.id := 999; o.inner.a := 999; }

fn main(): int64 {
    var o: Outer;
    o.id := 1; o.inner.a := 1;

    var k: Outer := o;  k.id := 2;
    PrintLn("Zuweisung:      o.id=", IntToStr(o.id));

    AlsWert(o);
    PrintLn("Wert-Parameter: o.id=", IntToStr(o.id), " o.inner.a=", IntToStr(o.inner.a));

    AlsRef(o);
    PrintLn("ref-Parameter:  o.id=", IntToStr(o.id), " o.inner.a=", IntToStr(o.inner.a));
    return 0;
}

Zuweisung:      o.id=1
Wert-Parameter: o.id=1 o.inner.a=1
ref-Parameter:  o.id=999 o.inner.a=999

Weder die Zuweisung noch der Wert-Parameter berühren oauch das verschachtelte inner nicht. Erst ref greift durch.

Was kopiert?
var b: T := a ja
b := a ja
bx.p := a ja
fn f(p: T) — Parameterübergabe ja (seit 1.0.20F)
fn f(ref p: T) nein — arbeitet auf dem Original des Aufrufers
var s: S := puffer as S nein — ein Cast ist eine Sicht auf vorhandenen Speicher
new nein

Dass der Cast nicht kopiert, ist Absicht und wird gebraucht: so legt die Standardbibliothek Binärformate über einen Puffer, ohne die Bytes zu bewegen.

 
Bis 1.0.20E änderte ein gewöhnlicher Struct-Parameter das Original des Aufrufers. Wer Code aus dieser Zeit übernimmt, prüft die Funktionen, die sich darauf verließen — sie brauchen jetzt ref am Parameter, sonst arbeiten sie auf einer Kopie und die Änderung verpufft. Betroffen waren 51 Funktionen der Standardbibliothek in 19 Units; die sind mit der Umstellung nachgezogen worden.

type Point = struct {
    x: f64;
    y: f64;

    fn Distance(other: Point): f64 {
        var dx := self.x - other.x;
        var dy := self.y - other.y;
        return Sqrt(dx * dx + dy * dy);
    }
};

var p1: Point;
p1.x := 0.0;
p1.y := 0.0;
var p2: Point;
p2.x := 3.0;
p2.y := 4.0;
PrintFloat(p1.Distance(p2));   // 5.0

var p3 := p1;   // seit 1.0.20A eine Kopie
p3.x := 9.0;    // p1.x bleibt 0.0

Daraus folgen drei Dinge, die in der Praxis regelmäßig überraschen:

1. Verschachtelte Structs zählen als Pointer. Ein Struct-Feld belegt 8 Byte, nicht die Größe des eingebetteten Typs:

type Outer = struct {
    id:    int64;
    inner: Inner;    // 8 Byte – Pointer, nicht inline
    buf:   Buffer;   // 8 Byte – Pointer, nicht inline
};
// sizeof(Outer) == 24, nicht 8 + sizeof(Inner) + sizeof(Buffer)

2. Eine Struct-Variable überlebt die Funktion. Da sie heap-alloziert ist, entsteht beim Zurückgeben kein hängender Pointer:

fn MakeInner(): Inner {
    var x: Inner;
    x.a := 3;
    return x;      // gültig – kein Dangling Pointer
}

3. Funktionen mutieren das Original. Ein Struct-Parameter wird by-reference übergeben:

fn Bump(o: Outer): void {
    o.inner.a := 42;    // wirkt beim Aufrufer
}

Fallstrick — nie feldweise in ein nicht zugewiesenes Struct-Feld schreiben. Solange ein verschachteltes Feld noch keinen Wert hat, ist der Pointer 0. arr[0].inner.a := 3 schreibt dann auf einen Null-Pointer → Segfault. Das Feld zuerst als ganze Struct zuweisen:
arr[0].inner := MakeInner(3, 9);   // richtig
arr[0].inner.a := 3;               // Segfault, wenn inner noch 0 ist

class – Referenztyp (Heap)

Klassen werden mit new auf dem Heap alloziert. Zuweisung kopiert den Pointer, nicht den Inhalt.

type Counter = class {
    value: int64;

    fn Create(start: int64) {
        self.value := start;
    }

    fn Increment() { self.value++; }
    fn Get(): int64 { return self.value; }
};

var c := new Counter(0);
c.Increment();
c.Increment();
PrintLn(IntToStr(c.Get()));  // 2
dispose c;

flat struct – ohne Padding

flat struct packt alle Felder dicht hintereinander, ohne Ausrichtungs-Padding. Sinnvoll für Serialisierung und Protokollpuffer, bei denen das Speicherbild exakt festliegen muss.

type Header = flat struct {
    magic:   uint32;
    version: uint8;
    flags:   uint8;
};

packed struct – Bit-Level-Layout

packed struct erlaubt die genaue Platzierung einzelner Felder über at(offset). Typischer Einsatz: Hardware-Register und Protokollköpfe mit festem Speicherbild.

Der Wert ist ein Byte-Offset, kein Bit-Offset: Die drei Felder unten liegen auf Byte 0, 1 und 2, sizeof(StatusReg) ergibt 3. Bitfelder innerhalb eines Bytes lassen sich damit nicht ausdrücken — dafür bleiben Maske und Schiebeoperation.

Überlappungen prüft der Compiler: Zwei Felder auf demselben Offset melden struct field at(offset) overlaps with previous field.

type StatusReg = packed struct {
    ready:  uint8 at(0);
    error:  uint8 at(1);
    mode:   uint8 at(2);
};

Beide Formen sind seit lyxc 1.0.11D verfügbar.

Seit lyxc 1.0.15G schreibt eine Feldzuweisung genau die Breite des Feldes (1, 2, 4 oder 8 Byte) — Nachbarfelder bleiben unberührt, und die Reihenfolge der Zuweisungen spielt keine Rolle. Lese- und Schreibpfad sind damit symmetrisch.

enum – Aufzählungstyp

Enums definieren benannte Ganzzahl-Konstanten. Der Compiler prüft Exhaustivität in match (Pattern Matching)-Ausdrücken — ein fehlender Zweig bricht den Bau ab: sema error: match does not cover variant 'Error' — add a case or a wildcard.

enum Direction { North, South, East, West }

enum HttpStatus {
    Ok        = 200,
    NotFound  = 404,
    ServerErr = 500
}

var dir := Direction.North;
var code := HttpStatus.Ok;

match (dir) {
    case Direction.North => Print("N");
    case Direction.South => Print("S");
    case Direction.East  => Print("E");
    case Direction.West  => Print("W");
}

Was der Compiler prüft — und was nicht

Nachgemessen mit lyxc 1.1.14A:

Fall Ergebnis
var s: Status := 99; abgewiesen (Initialisierung: Struct/Klasse erwartet, int64 gegeben — die Meldung nennt fälschlich Struct/Klasse)
zeige(42) an fn zeige(s: Status) abgewiesen
Status.Gibtsnicht abgewiesen (unbekannter Enum-Member)
match ohne alle Varianten und ohne _ abgewiesen
var a: A := B.Q; (anderes Enum) angenommen — siehe Kasten
A.Y == B.Q wahr — beide sind 1
enum Doppelt { A = 1, B = 1 } angenommen, ohne Warnung
enum E { X = -1 } abgewiesen: der Wert von X liegt ausserhalb von 0..4294967295 — Enum-Werte sind vorzeichenlos 32 Bit; für negative Fehlercodes taugt ein Enum nicht
sizeof auf den Enum-Typ 8 (gespeichert wird ein 64-Bit-Wort)
 
Zwei Löcher, die im sicherheitskritischen Code zählen:

* Die Zuweisung über Enum-Grenzen ist inzwischen abgewiesen (#1883, nachgemessen mit lyxc 1.2.2B): var a: A := B.Q; bricht mit sema error: Initialisierung: anderer benannter Typ ab. Der Vergleich nichtA.Y == B.Q übersetzt weiterhin und ist wahr. Verglichen wird dort die Zahl, nicht die Bedeutung, und das geht in beide Richtungen schief:

enum Fahrwerk  { Eingefahren, Ausgefahren }   // 0, 1
enum Triebwerk { Aus, Laeuft }                // 0, 1
enum Klappen   { Ein = 1, Aus = 0 }           // andersherum

Fahrwerk.Ausgefahren == Triebwerk.Laeuft      // wahr  — gleiche Zahl, anderer Sinn
Fahrwerk.Eingefahren == Triebwerk.Aus         // wahr
Fahrwerk.Ausgefahren == Klappen.Aus           // falsch — gleicher Sinn, andere Zahl


Besonders unangenehm ist der zweite Fall: welche Zahl ein Name trägt, entscheidet die Reihenfolge der Aufzählung. Wer einen Zustand in der Mitte einfügt, verschiebt alle folgenden Werte und kippt damit stillschweigend jeden typübergreifenden Vergleich — dort, wo match sonst gerade davor schützt.
* as in einen Enum-Typ wird nicht geprüft (#1884). lies() as Status nimmt jeden Wert an; ein so entstandener Wert fällt anschließend durch ein vollständiges match hindurch, ohne dass irgendein Zweig greift und ohne Meldung. Die Zusage „match ist vollständig„ gilt damit nur für Werte, die nicht durch einen Cast entstanden sind.

Wer einen Enum-Wert aus einer fremden Quelle (Datei, Netz, Sensor) übernimmt, prüft ihn deshalb selbst, bevor er castet — oder führt einen _-Zweig mit, der den Fehlerfall behandelt, statt sich auf die Vollständigkeit zu verlassen.
Merkmal struct class enum
Speicherort Stack Heap Code (Immediate)
Zuweisung Kopiert Wert Kopiert Pointer Kopiert Wert
Freigabe Automatisch (Scope-Ende) dispose Automatisch
Methoden
Vererbung ✅ (extends)

12. Range-Typen (Sicherheitskritisch)

Lyx erlaubt die Definition von Integer-Typen mit eingeschränktem Wertebereich. Sie sind ein Kernmerkmal für DO-178C-konforme Software.

type Altitude  = int64 range -1000..60000;   // Meter ü. NN
type Speed     = int64 range 0..300;          // km/h
type DalLevel  = int8  range 1..5;
type Temp      = int32 range -273..1000;      // Celsius

Compile-Zeit-Prüfung

Literale außerhalb des Bereichs erzeugen einen Fehler:

var alt: Altitude := 70000;   // ✗ Compiler-Fehler: 70000 liegt außerhalb -1000..60000
var spd: Speed    := -10;     // ✗ Compiler-Fehler: -10 liegt außerhalb 0..300

Laufzeit-Prüfung

Nicht-konstante Werte werden durch eingefügte Prüfungen überwacht:

var raw := ReadSensor();         // Laufzeit-Wert, unbekannt
var alt: Altitude := raw;        // Compiler fügt Bereichsprüfung ein

Die Prüfung greift an vier Stellen; jede meldet, an welcher (gemessen mit lyxc 1.1.14A), und bricht mit Exit 1 ab:

Stelle Meldung
Initialisierung Bereichstyp Kurs: Wert ausserhalb von 0..360 (Initialisierung)
Zuweisung … (Zuweisung)
Aufrufargument … (Parameter)
Rückgabewert … (Rueckgabe)
 
Ein konstantes Argument außerhalb des Bereichs meldet der Compiler (#1880, nachgemessen mit lyxc 1.2.2B): var k: Kurs := 400; bei type Kurs = int64 range 0..359 bricht mit sema error: Wert liegt ausserhalb des Bereichs des Typs ab. Früher fiel das erst zur Laufzeit auf — und nur mit --runtime-checks.

Range-Typen in Safety-Code

@dal(B)
@flight_crit
unit flightcontrol;

type Altitude  = int64 range -1000..60000;
type BankAngle = int32 range -60..60;      // Grad

fn SetAltitude(target: Altitude) {
    // Innerhalb der Funktion ist der Bereich zugesichert: der Wächter am
    // Parameter hat ihn geprüft, bevor der Rumpf läuft
    ActuatorSetAltitude(target);
}

 
DO-178C-Hinweis:
Range-Typen erfüllen die Anforderung an Datensicherheit (Data Coupling, DO-178C Abschnitt 6.3.2). Sie verhindern ungültige Systemzustände durch Prüfung zur Compile- und Laufzeit; die Laufzeitverletzung beendet das Programm, statt mit einem falschen Wert weiterzulaufen.

Gewollter Überlauf: ''wraps'' statt ''range''

Ein Kurs, ein Winkel, ein Zeigerindex im Ringpuffer soll bei der Obergrenze nicht abbrechen, sondern umlaufen. Dafür gibt es wraps — allerdings nicht an einem Bereichstyp, sondern nur an einem Einheitentyp (utype):

dim Winkel;
utype Grad: Winkel = 1 wraps 0..359;

fn main(): int64 {
    var kurs: Grad := 350;
    var dreh: Grad := 20;
    var neu:  Grad := kurs + dreh;   // 10 — der Umlauf ist die Rechnung, kein Fehler
    return 0;
}

Gemessen mit lyxc 1.1.14A, mit 1.1.18A nachgeprüft — die Tabelle gilt für die Zuweisung an eine Variable des Typs:

Rechnung Ergebnis
350 + 20 10
350 + 300 290 (mehrfacher Umlauf wird mitgerechnet)
350 * 3 330
0 − 1 359 (auch nach unten)
bei wraps -180..179: 179 + 1 -180
 
Gewrappt wird bei der Zuweisung an eine Variable des Typs (#2004 behoben, nachgemessen 2026-09-08). Bei wraps 0..360 ergibt 361 den Wert 0, 362 den Wert 1, 370 den Wert 9 und 720 den Wert 359 — der Bereich umfasst 361 Werte. Konstanten und zur Laufzeit bestimmte Werte verhalten sich gleich.

13. Typ-Inferenz

Wenn der Typ aus dem Initialisierer eindeutig bestimmbar ist, kann die Typ-Annotation weggelassen werden:

var x := 42;           // int64 (Standard-Integer)
var f := 3.14;         // f64  (Standard-Float)
var b := true;         // bool
var s := "Hallo";      // pchar
var i := 42;        // int32 (durch Suffix)
var u := 0xFF;       // uint8 (durch Hex + Suffix)

Seit lyxc 1.0.15E trägt die Inferenz auch Klassen und Structs aus einer Funktionsrückgabe. Feldzugriff, Methodenaufruf und Operator-Überladung arbeiten damit ohne ausgeschriebenen Typ:

var p := QBoolNew(0.75);          // p.probability ergibt 0.75
var c := a + b;                   // c trägt die Klasse des linken Operanden

 
Bei array&lt;T&gt; aus einer Funktionsrückgabe geht der Typ weiterhin verloren. var a := MkA(); liefert für len(a) eine 0 und für a[i] eine beliebige Zahl — die Variable hält die nackte Adresse. Den Typ hier ausschreiben: var a: array&lt;int64&gt; := MkA();.

Explizite Annotation ist erforderlich bei array&lt;T&gt; (Arrays & Maps) aus einer Funktionsrückgabe (siehe Kasten), und empfohlen, wenn:

14. Typ-Konvertierung (as)

Alle Typkonvertierungen sind explizit. Der as-Operator führt die Konvertierung durch.

var i: int64 := 1000;
var b: uint8 := i as uint8;    // Truncation: 1000 mod 256 = 232
var f: f64   := i as f64;      // 1000.0
var r: int32 := i as int32;    // 1000 (passt in int32)

// Adressen brauchen keinen Cast — sie sind bereits int64
var addr: int64 := 0x40020000;
var wert: int64 := peek32(addr);   // 4 Byte an dieser Adresse lesen

Konvertierungs-Regeln

Von → Nach Verhalten
intN → breiterer int Vorzeichen-Erweiterung (sign-extend)
intN → schmälerer int Abschneiden (truncation, modular)
intNuintN Bit-Reinterpretation (kein Wert-Check)
intNf64 Exakte Konvertierung (bis ±2⁵³)
f64intN Abschneiden Richtung Null (floor toward zero)
f32f64 Präzisions-Erweiterung (verlustfrei)
f64f32 Präzisions-Verlust (rounding to nearest)
Pointer → int64 Adress-Wert als Integer
int64 → Pointer Integer als Adresse (nur in unsafe oder MMIO)
 
Linter-Warnung bei riskantem Cast:
–lint warnt bei Konvertierungen, die Datenverlust verursachen können (f64 → int32, int64 → uint8 ohne vorherige Prüfung). In @dal(A)-Modulen sind solche Casts ohne expliziten Kommentar ein Compiler-Fehler.

15. Typ-Prüfung (is)

Der is-Operator prüft den Laufzeit-Typ eines Objekts (nur für Klassen mit Vererbung):

type Shape  = class { virtual fn Area(): f64; };
type Circle = class extends Shape { radius: f64; ... };
type Rect   = class extends Shape { w: f64; h: f64; ... };

fn Describe(s: Shape) {
    if (s is Circle) {
        var c := s as Circle;   // Sicherer Cast nach is-Prüfung
        PrintFloat(c.radius);
    } else if (s is Rect) {
        var r := s as Rect;
        PrintFloat(r.w);
    }
}

16. Speicherklassen (Kurzübersicht)

Keyword Änderbar Beschreibung
var Standard-Variable; kann jederzeit neu zugewiesen werden
let ❌ (nach Init) Einmalige Laufzeit-Zuweisung; danach read-only
co Read-only Stack-Slot
con Compile-Zeit-Konstante; wird direkt eingebettet (Immediate)

→ Details: Variablen – Speicherklassen, Gültigkeit, Lebensdauer · Syntax-Referenz

17. Vollständige Typ-Übersicht

Kategorie Typen Speicherort ABI-Register
Ganzzahl (signiert) int8/i8, int16/i16, int32/i32, int64/i64 Stack / Register GPR
Ganzzahl (unsigniert) uint8/u8, uint16/u16, uint32/u32, uint64/u64 Stack / Register GPR
Fließkomma f32, f64 Stack / Register FPU
Wahrheitswert bool Stack / Register GPR (0/1)
Zeichen char Stack / Register GPR
String pchar Stack (Pointer) GPR
Adresse / Nullable int64 (es gibt keinen Zeigertyp), Suffix T? Stack (8 Byte) GPR
Festes Array [N]T bzw. T[N], bis zu zwei Dimensionen Stack – (Fat Pointer)
Dynamisches Array array&lt;T&gt;, array[T], Array&lt;T&gt; Heap
Ringpuffer RingBuffer&lt;T&gt; Heap
SIMD-Array parallel Array&lt;T&gt; Heap (aligned)
Assoziativ Map&lt;K,V&gt; — nur ganzzahlige Schlüssel Heap
Tupel (T, T) bzw. [T, T] — genau zwei Elemente Register rax/rdx
Funktionszeiger fn(…): T Stack (8 Byte) GPR
Methodenzeiger method(…): T Stack (16 Byte: Code + Daten) GPR
Werttyp struct Stack
Referenztyp class Heap GPR (Pointer)
Aufzählung enum Code (Immediate) GPR
Bereichstyp type X = intN range a..b wie Basistyp wie Basistyp

18. Implementierungsstatus (V1.0.17K)

Feature Status
Alle Integer-Typen (int8–int64, uint8–uint64) samt Kurzformen i8–i64 / u8–u64 ✅ Vollständig
f32, f64 (IEEE 754) ✅ Vollständig
bool, char, pchar ✅ Vollständig
QBool aus std.qbool ✅ Vollständig — Bibliothekstyp, kein eingebautes qbool und keine 0.5q-Literale
[N]T feste Arrays (Fat Pointer) ✅ Vollständig
[N][M]T zweidimensionale Arrays ✅ Genau eine Verschachtelung — eine dritte Dimension meldet Array mit mehr als zwei Dimensionen ist nicht umgesetzt. Eine ganze Zeile zuzuweisen (m[i] := x) hat keine Bedeutung und wird nicht gemeldet
array&lt;T&gt; dynamische Arrays, new T[n] ✅ Vollständig
RingBuffer&lt;T&gt; ✅ Deklarierbar
parallel Array&lt;T&gt; (SIMD) ✅ Vollständig
Map&lt;K,V&gt; Hash-Map ⚠️ Nur ganzzahlige Schlüssel; kein delete, keine Iteration
Set&lt;T&gt; ❌ Lexer und Parser kennen es, die Semantik nicht: unknown type in var decl 'Set'
Tupel (T, T) ✅ Genau zwei Elemente (Aufrufkonvention trägt rax/rdx)
Funktionszeiger fn(…), Methodenzeiger method(…) ✅ Vollständig
Range-Typen (Compile-Zeit + Laufzeit) ✅ Vollständig
struct, class, enum ✅ Vollständig
int, uint ❌ Existieren nicht — nur die breitenbehafteten Typen
isize, usize ❌ Reserviert, nicht implementiert
any-Wildcard ❌ Existiert nicht: unknown type in var decl 'any'
Typ-Inferenz ⚠️ Skalare, Klassen und Structs — bei array&lt;T&gt; aus einer Funktionsrückgabe geht der Typ verloren
as-Cast (alle Kombinationen) ✅ Vollständig
is-Typ-Test ✅ Vollständig
Aerospace-Garantien (Range-Typen, @redundant) ✅ Stabil
Cross-Arch (x86_64, ARM64, RISC-V64) ✅ Vollständig

Weiterführende Seiten:

Letzte Aktualisierung: 2026-09-05 (#1883 und #1880, nachgemessen mit lyxc 1.2.2B) — Enum-Kasten präzisiert: die Zuweisung über Enum-Grenzen wird jetzt abgewiesen, der Vergleich weiterhin nicht; Bereichstyp-Kasten gezogen (konstante Werte außerhalb des Bereichs meldet schon die Übersetzung).

Vorherige letzte Aktualisierung: 2026-09-05 (#1880, nachgemessen mit lyxc 1.2.2B) — Bereichstyp-Kasten gezogen: konstante Werte außerhalb des Bereichs meldet schon die Übersetzung.

Vorherige letzte Aktualisierung: 2026-09-05 (#1956, gemessen mit lyxc 1.1.18A) — wraps-Abschnitt ergänzt: gewrappt wird bei der Zuweisung an eine Variable des Typs, nicht im freien Ausdruck.

Vorherige letzte Aktualisierung: 2026-08-13 — Stand auf lyxc 1.0.21A gezogen.

Letzte Aktualisierung: 2026-08-19 — Abschnitt „Literale: Reichweite und Genauigkeit“ (#1657, #1662) ergänzt, mit lyxc 1.1.3I nachgemessen.

Letzte Aktualisierung: 2026-08-30 — Enums gegen lyxc 1.1.14A durchgemessen: Abschnitt „Was der Compiler prüft — und was nicht„ ergänzt (fehlende Typtrennung zwischen Enums #1883, ungeprüfter as-Cast #1884, vorzeichenlose 32-Bit-Werte, doppelte Werte ohne Warnung).

Zuvor 2026-08-30 — Range-Typen gegen lyxc 1.1.14A durchgemessen: die vier Prüfstellen (Initialisierung, Zuweisung, Parameter, Rückgabe) belegt, konstantes Argument als Lücke vermerkt (#1880); neuer Abschnitt „Gewollter Überlauf: wraps statt range“ samt Modulo-361-Falle.

Vorherige Aktualisierung: 2026-08-18 — Abschnitt „Arrays von Structs“ ergänzt (#1597, #1612, #1636); Beispiel mit lyxc 1.1.2E übersetzt und ausgeführt.

Codebeispiele geprüft: gegen lyxc 1.2.5C übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die pub fn-Signaturen in aurum/std gehalten, 0 Abweichungen).