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 auf0.
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.
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 blankesintgibt es nicht — ebenso wenig wieuint. Es existieren nur die vier breitenbehafteten Typen oben mit ihren Kurzformen;var x: int := 42;meldetunknown type in var decl 'int'. Wer die Standard-Ganzzahl meint, schreibtint64(oderi64).
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");
}
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;
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 {
// ...
}
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;
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;
}
| 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 entstehendeNaN/Inf-Operation löst einenpanicaus, statt still einen ungültigen Wert weiterzupropagieren.
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!)
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
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 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 |
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
Lyx unterscheidet zwei Array-Formen mit unterschiedlichem Speicherort und Größenverhalten.
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.
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:
[N]Struct Müll, während [N]int64 an derselben Stelle stimmte (#1597).arr[i] := s schrieb den Zeiger in den Slot statt den Inhalt zu kopieren — Element und Quelle teilten danach dasselbe Objekt (#1612). Jetzt wird kopiert; ist der Slot leer, bleibt es beim Zeiger.
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
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 istarray<T>verboten (Heap-Allokation). Dort ausschließlich[N]T-Arrays auf dem Stack verwenden.
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<T> | Heap | Dynamisch | ✅ Ja | ❌ Verboten |
parallel Array<T> | Heap (SIMD-aligned) | Dynamisch | ✅ Ja | ❌ Verboten |
Map<K, V> 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<pchar, V> wird abgewiesen — Details und Ausweg unter Arrays & Maps. delete und for key, val in m sind nicht umgesetzt.
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 o — auch 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 jetztrefam 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 := 3schreibt 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
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 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 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.
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");
}
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 mitsema error: Initialisierung: anderer benannter Typab. Der Vergleich nicht —A.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, womatchsonst gerade davor schützt.
*asin einen Enum-Typ wird nicht geprüft (#1884).lies() as Statusnimmt jeden Wert an; ein so entstandener Wert fällt anschließend durch ein vollständigesmatchhindurch, ohne dass irgendein Zweig greift und ohne Meldung. Die Zusage „matchist 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) | ❌ |
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
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
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;beitype Kurs = int64 range 0..359bricht mitsema error: Wert liegt ausserhalb des Bereichs des Typsab. Früher fiel das erst zur Laufzeit auf — und nur mit--runtime-checks.
@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.
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). Beiwraps 0..360ergibt 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.
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
Beiarray<T>aus einer Funktionsrückgabe geht der Typ weiterhin verloren.var a := MkA();liefert fürlen(a)eine0und füra[i]eine beliebige Zahl — die Variable hält die nackte Adresse. Den Typ hier ausschreiben:var a: array<int64> := MkA();.
Explizite Annotation ist erforderlich bei array<T> (Arrays & Maps) aus einer Funktionsrückgabe (siehe Kasten), und empfohlen, wenn:
int32 statt int64)con-Konstanten deklariert werden
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
| Von → Nach | Verhalten |
|---|---|
intN → breiterer int | Vorzeichen-Erweiterung (sign-extend) |
intN → schmälerer int | Abschneiden (truncation, modular) |
intN → uintN | Bit-Reinterpretation (kein Wert-Check) |
intN → f64 | Exakte Konvertierung (bis ±2⁵³) |
f64 → intN | Abschneiden Richtung Null (floor toward zero) |
f32 → f64 | Präzisions-Erweiterung (verlustfrei) |
f64 → f32 | 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:
–lintwarnt 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.
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);
}
}
| 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
| 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<T>, array[T], Array<T> | Heap | – |
| Ringpuffer | RingBuffer<T> | Heap | – |
| SIMD-Array | parallel Array<T> | Heap (aligned) | – |
| Assoziativ | Map<K,V> — 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 |
| 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<T> dynamische Arrays, new T[n] | ✅ Vollständig |
RingBuffer<T> | ✅ Deklarierbar |
parallel Array<T> (SIMD) | ✅ Vollständig |
Map<K,V> Hash-Map | ⚠️ Nur ganzzahlige Schlüssel; kein delete, keine Iteration |
Set<T> | ❌ 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<T> 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).