====== 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).