====== 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 | [[https://github.com/SEOLizer/LyX-Compiler/issues/1657|#1657]] | | ''1.0e308'' | 1 ULP daneben | bitgenau | [[https://github.com/SEOLizer/LyX-Compiler/issues/1657|#1657]] | | ''1.0e-320'' (subnormal) | still ''0.0'' | 2024 (Bitmuster) | [[https://github.com/SEOLizer/LyX-Compiler/issues/1662|#1662]] | | ''4.9e-324'' (kleinstes subnormales) | still ''0.0'' | 1 (Bitmuster) | [[https://github.com/SEOLizer/LyX-Compiler/issues/1662|#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: [[lyx_-_programmiersprache:guides:das-energy-aware-programmiermodell|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'' ([[lyx_-_programmiersprache:sprache:rohspeicher|Rohspeicher]]) angelegte Zelle durch. → Details: [[lyx_-_programmiersprache:sprache:pointer-inlining|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: * **Als Funktionsparameter** lieferte ''%%[N]Struct%%'' Müll, während ''%%[N]int64%%'' an derselben Stelle stimmte ([[https://github.com/SEOLizer/LyX-Compiler/issues/1597|#1597]]). * **''arr[i] := s''** schrieb den Zeiger in den Slot statt den Inhalt zu kopieren — Element und Quelle teilten danach dasselbe Objekt ([[https://github.com/SEOLizer/LyX-Compiler/issues/1612|#1612]]). Jetzt wird kopiert; ist der Slot leer, bleibt es beim Zeiger. * **Als Klassenfeld** stürzte der schreibende Zugriff ab ([[https://github.com/SEOLizer/LyX-Compiler/issues/1636|#1636]]). 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 – Dynamisches Array (Heap) ==== Größe wächst zur Laufzeit. Lebt auf dem Heap; muss explizit freigegeben werden. var list: array := [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<T>'' verboten** (Heap-Allokation). Dort ausschließlich ''[N]T''-Arrays auf dem Stack verwenden. ==== parallel Array – SIMD-optimiert ==== Für SIMD-Verarbeitung (AVX2, NEON) existiert eine spezielle Array-Form mit garantierter Ausrichtung: var samples: parallel Array(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 | ===== 10. Map – Assoziatives Array ===== ''Map<K, V>'' ist eine Hash-Map mit **ganzzahligem** Schlüssel und typisiertem Wert. Literale werden mit ''{key: value}''-Syntax geschrieben. var scores: Map := { 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 [[lyx_-_programmiersprache:sprache:arrays#4. Maps — Map|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 ''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 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'' ([[lyx_-_programmiersprache:sprache:pattern-matching|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** ([[https://github.com/SEOLizer/LyX-Compiler/issues/1883|#1883]], nachgemessen mit lyxc 1.2.2B): ''%%var a: A := B.Q;%%'' bricht mit ''sema error: Initialisierung: anderer benannter Typ'' ab. **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, wo ''match'' sonst gerade davor schützt. > * **''as'' in einen Enum-Typ wird nicht geprüft** ([[https://github.com/SEOLizer/LyX-Compiler/issues/1884|#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** ([[https://github.com/SEOLizer/LyX-Compiler/issues/1880|#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** ([[https://github.com/SEOLizer/LyX-Compiler/issues/2004|#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<T>'' 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<int64> := MkA();''. Explizite Annotation ist **erforderlich** bei ''array<T>'' ([[lyx_-_programmiersprache:sprache:arrays|Arrays & Maps]]) aus einer Funktionsrückgabe (siehe Kasten), und empfohlen, wenn: * Der Typ nicht auf den ersten Blick klar ist * Ein präziserer Typ gewünscht ist (''int32'' statt ''int64'') * Range-Typen oder ''con''-Konstanten deklariert werden ===== 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) | | ''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:** > ''--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: [[lyx_-_programmiersprache:sprache:variablen|Variablen – Speicherklassen, Gültigkeit, Lebensdauer]] · [[lyx_-_programmiersprache:sprache:syntax|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<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 | ===== 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<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:** * [[lyx_-_programmiersprache:sprache:syntax|Syntax-Referenz – Operatoren, Literale, Speicherklassen]] * [[lyx_-_programmiersprache:sprache:memory-management|Memory Management – Stack, Heap, std.alloc]] * [[lyx_-_programmiersprache:sprache:pointer-inlining|Low-Level: Speicherzugriff & Pragmas]] * [[lyx_-_programmiersprache:sprache:generics-traits|Generics & Interfaces]] * [[lyx_-_programmiersprache:sprache:exception-handling|Fehlerbehandlung – SafeAdd, Result, Option]] * [[lyx_-_programmiersprache:guides:do-178c|DO-178C – Range-Typen und Datensicherheit]] * [[lyx_-_programmiersprache:guides:das-energy-aware-programmiermodell|Energy-Aware – qbool und QBool-Operationen]] Letzte Aktualisierung: 2026-09-05 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1883|#1883]] und [[https://github.com/SEOLizer/LyX-Compiler/issues/1880|#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 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1880|#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 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1956|#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" ([[https://github.com/SEOLizer/LyX-Compiler/issues/1657|#1657]], [[https://github.com/SEOLizer/LyX-Compiler/issues/1662|#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 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1597|#1597]], [[https://github.com/SEOLizer/LyX-Compiler/issues/1612|#1612]], [[https://github.com/SEOLizer/LyX-Compiler/issues/1636|#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).