====== Lyx – Datentypen ======
Stand: v0.9.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 ^ Bits ^ Wertebereich ^ Literal ^ Typisches Einsatzgebiet ^
| ''int8'' | 8 | −128 … 127 | ''42i8'' | Kompakte Felder, Protokoll-Bytes mit Vorzeichen |
| ''int16'' | 16 | −32 768 … 32 767 | ''42i16'' | Audio-Samples, kompakte Sensordaten |
| ''int32'' | 32 | −2 147 483 648 … 2 147 483 647 | ''42i32'' | Koordinaten, API-Rückgabewerte |
| ''int64'' | 64 | −9 223 372 036 854 775 808 … 9 223 372 036 854 775 807 | ''42'' | Standard-Ganzzahl in Lyx |
| ''int'' | 64 | Alias für ''int64'' | ''42'' | Kurzform, identisch mit int64 |
var a: int64 := 9_000_000_000; // Unterstriche als Tausender-Trenner
var b: int8 := -100i8;
var c: int32 := 2_147_483_647i32;
// Überlauf-sicheres Addieren (std.result)
import std.result;
var result := SafeAdd(a, 1);
if (ResultInt64IsOk(result)) {
PrintInt(Unwrap(result));
} else {
PrintStr("Ü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 ^ Bits ^ Wertebereich ^ Literal ^ Typisches Einsatzgebiet ^
| ''uint8'' | 8 | 0 … 255 | ''42u8'' | Bytes, MMIO-Register, Protokoll-Rohdaten |
| ''uint16'' | 16 | 0 … 65 535 | ''42u16'' | Ports, Checksummen, kompakte Flags |
| ''uint32'' | 32 | 0 … 4 294 967 295 | ''42u32'' | IPv4-Adressen, CRC32, Hardware-Register |
| ''uint64'' | 64 | 0 … 18 446 744 073 709 551 615 | ''42u64'' | Datei-Offsets, Hash-Werte, 64-Bit-Masken |
// Bit-Manipulation (typischer uint32-Einsatz)
var flags: uint32 := 0x00u32;
con FLAG_READ: uint32 := 0x01u32;
con FLAG_WRITE: uint32 := 0x02u32;
con FLAG_EXEC: uint32 := 0x04u32;
flags := flags | FLAG_READ | FLAG_WRITE; // Bits setzen
var canRead := flags & FLAG_READ != 0u32; // Bit testen
flags := flags & ~FLAG_WRITE; // Bit löschen
// Hex-Literale für MMIO
var GPIO_BASE: ^uint32 := 0x3FF44000 as ^uint32;
===== 3. Plattform-abhängige Typen =====
^ Typ ^ Beschreibung ^ Typisches Einsatzgebiet ^
| ''isize'' | Pointer-Größe, signiert (64-Bit auf allen unterstützten Plattformen) | Pointer-Differenz, Offsets |
| ''usize'' | Pointer-Größe, unsigniert | Array-Indizes, Speichergrößen |
''usize'' ist der empfohlene Typ für Array-Indizes und Längenangaben, da er auf der Zielplattform immer groß genug für jeden Speicherzeiger ist.
var len: usize := 1024;
for i := 0 to (len as int64) - 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.14f32'' |
| ''f64'' | 64 | ~15 Dezimalstellen | ±1.8 × 10³⁰⁸ | ''3.14'' |
var pi: f64 := 3.14159265358979;
var small: f32 := 1.5f32;
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;
==== 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, nicht Zuweisung!)
===== 6. Probabilistischer Typ – qbool =====
''qbool'' ist Lyxs probabilistischer Wahrheitswert: ein ''f64''-Wert im Bereich 0.0 (sicher falsch) bis 1.0 (sicher wahr). Er ermöglicht unscharfe Logik in KI-Anwendungen und IoT-Sensorfusion.
^ Literal ^ Bedeutung ^
| ''0.0q'' | Definitiv falsch |
| ''0.5q'' | Unbekannt / gleichwahrscheinlich |
| ''1.0q'' | Definitiv wahr |
| ''0.75q'' | Wahrscheinlich wahr (75 %) |
import std.qbool;
var smoke: qbool := 0.8q; // Rauchsensor: 80 % sicher
var heat: qbool := 0.9q; // Temperatursensor: 90 % sicher
var alarm := QBoolAnd(smoke, heat); // 0.72q (kombiniert)
var alert := Observe(alarm); // bool: true (über Schwellwert)
// Operationen
var neg := QBoolNot(smoke); // 0.2q
var either := QBoolOr(smoke, heat); // 0.98q
var xor := QBoolXor(smoke, heat); // |0.8 - 0.9| = 0.1q
In der ABI wird ''qbool'' als ''f64'' behandelt (Float-Register XMM0/V0/fa0).
→ Details: [[lyx_-_programmiersprache: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
PrintStr(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. Pointer-Typen =====
Pointer speichern Speicheradressen. Alle Pointer-Typen sind intern 8-Byte-Integer (''int64'').
^ Notation ^ Bedeutung ^
| ''%%^T%%'' | Pointer-Typ auf T |
| ''%%^x%%'' | Adresse von x (Address-of-Operator) |
| ''%%p^%%'' | Wert an Adresse p (Dereferenz) |
| ''nil'' | Null-Pointer (kein gültiges Ziel) |
| ''T?'' | Nullable Typ (darf nil sein) |
var x: int64 := 42;
var p: ^int64 := ^x; // p zeigt auf x
p^ := 99; // x ist jetzt 99
// Nullable
var opt: pchar? := GetOptionalName();
var name: pchar := opt ?? "Gast"; // Fallback wenn nil
→ Details: [[lyx_-_programmiersprache:pointer-inlining|Pointer & Inlining – vollständige Referenz]]
===== 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.
==== 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);
PrintInt(list.len()); // 7
PrintInt(list[0]); // 1
> **In ''@flight_crit''- und ''@dal(A)''-Modulen ist ''array'' 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 signal: parallel Array(1024); // SIMD-ausgerichtetes Array
@parallel
for i := 0 to 1023 do {
signal[i] := signal[i] * 2.0f32; // 8 Elemente pro AVX2-Instruktion
}
^ Typ ^ Speicherort ^ Größe ^ Bounds-Check ^ Safety-Code ^
| ''[N]T'' | Stack / Data | Compile-Zeit-Konstant | ✅ Ja | ✅ Erlaubt |
| ''array'' | Heap | Dynamisch | ✅ Ja | ❌ Verboten |
| ''parallel Array'' | Heap (SIMD-aligned) | Dynamisch | ✅ Ja | ❌ Verboten |
===== 10. Map – Assoziatives Array =====
''Map'' ist eine Hash-Map mit typisiertem Schlüssel und Wert. Literale werden mit ''{key: value}''-Syntax geschrieben.
var scores: Map := {
"Alice": 95,
"Bob": 87,
"Carol": 92
};
scores["Dave"] := 78; // Einfügen / Überschreiben
var val := scores["Alice"]; // 95
var has := scores.Has("Eve"); // false
scores.Delete("Bob");
PrintInt(scores.Len()); // 3
===== 11. Verbundtypen =====
==== struct – Werttyp (Stack) ====
Structs sind stack-allozierte Werttypen. Zuweisung kopiert den gesamten Inhalt.
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 { x: 0.0, y: 0.0 };
var p2 := Point { x: 3.0, y: 4.0 };
PrintFloat(p1.Distance(p2)); // 5.0
var p3 := p1; // Kopie – p3 und p1 sind unabhängig
==== 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();
PrintInt(c.Get()); // 2
dispose c;
==== enum – Aufzählungstyp ====
Enums definieren benannte Ganzzahl-Konstanten. Der Compiler prüft Exhaustivität in ''match''-Ausdrücken.
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 => PrintStr("N");
case Direction::South => PrintStr("S");
case Direction::East => PrintStr("E");
case Direction::West => PrintStr("W");
}
^ 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 Zuweisungen werden durch IR-Checks überwacht:
var raw := ReadSensor(); // Laufzeit-Wert, unbekannt
var alt: Altitude := raw; // Compiler fügt Bereichsprüfung ein:
// if raw < -1000 or raw > 60000 → panic
==== 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) {
// Wertebereich ist zur Compile-Zeit garantiert – kein zusätzlicher nil-Check nötig
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 strikte Prüfung zur Compile- und Laufzeit. Der ''--lint''-Pass meldet alle Stellen, an denen ein Range-Typ mit einem ungeprüften Wert belegt wird.
===== 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 p := 0.75q; // qbool
var i := 42i32; // int32 (durch Suffix)
var u := 0xFFu8; // uint8 (durch Hex + Suffix)
Explizite Annotation ist 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)
// Pointer-Casts
var addr: int64 := 0x40020000;
var reg: ^uint32 := addr as ^uint32; // Integer → Pointer
var back: int64 := reg as int64; // Pointer → Integer
==== 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) =====
Die Speicherklasse bestimmt Mutabilität und Lebenszeit einer Variable:
^ 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:syntax|Syntax-Referenz – Speicherklassen]]
===== 17. Vollständige Typ-Übersicht =====
^ Kategorie ^ Typen ^ Speicherort ^ ABI-Register ^
| Ganzzahl (signiert) | ''int8'', ''int16'', ''int32'', ''int64'' / ''int'' | Stack / Register | GPR |
| Ganzzahl (unsigniert) | ''uint8'', ''uint16'', ''uint32'', ''uint64'' | Stack / Register | GPR |
| Plattform | ''isize'', ''usize'' | Stack / Register | GPR |
| Fließkomma | ''f32'', ''f64'' | Stack / Register | FPU |
| Wahrheitswert | ''bool'' | Stack / Register | GPR (0/1) |
| Probabilistisch | ''qbool'' | Stack / Register | FPU (als f64) |
| Zeichen | ''char'' | Stack / Register | GPR |
| String | ''pchar'' | Stack (Pointer) | GPR |
| Pointer | ''%%^T%%'', ''T?'' | Stack (8 Byte) | GPR |
| Festes Array | ''[N]T'' | Stack | – (Fat Pointer) |
| Dynamisches Array | ''array'' | Heap | – |
| SIMD-Array | ''parallel Array'' | Heap (aligned) | – |
| Assoziativ | ''Map'' | Heap | – |
| 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 |
| Wildcard | ''any'' | – | – (Generics) |
===== 18. Implementierungsstatus (v0.9.0) =====
^ Feature ^ Status ^
| Alle Integer-Typen (int8–int64, uint8–uint64) | ✅ Vollständig |
| f32, f64 (IEEE 754) | ✅ Vollständig |
| bool, char, pchar | ✅ Vollständig |
| qbool + QBool-Operationen | ✅ Vollständig |
| ''[N]T'' feste Arrays (Fat Pointer) | ✅ Vollständig |
| ''array'' dynamische Arrays | ✅ Vollständig |
| ''parallel Array'' (SIMD) | ✅ Vollständig |
| ''Map'' Hash-Map | ✅ Vollständig |
| Range-Typen (Compile-Zeit + Laufzeit) | ✅ Vollständig |
| struct, class, enum | ✅ Vollständig |
| isize, usize | ✅ Vollständig |
| Typ-Inferenz | ✅ Vollständig |
| ''as''-Cast (alle Kombinationen) | ✅ Vollständig |
| ''is''-Typ-Test | ✅ Vollständig |
| ''any''-Wildcard (Generics) | ⚠️ Partial |
| Aerospace-Garantien (Range-Typen, @redundant) | ✅ Stabil |
| Cross-Arch (x86_64, ARM64, RISC-V64) | ✅ Vollständig |
**Weiterführende Seiten:**
* [[lyx_-_programmiersprache:syntax|Syntax-Referenz – Operatoren, Literale, Speicherklassen]]
* [[lyx_-_programmiersprache:memory-management|Memory Management – Stack, Heap, std.alloc]]
* [[lyx_-_programmiersprache:pointer-inlining|Pointer & Inlining – ^T, Dereferenz, @volatile]]
* [[lyx_-_programmiersprache:generics-traits|Generics & Traits – Typen mit Schranken]]
* [[lyx_-_programmiersprache:exception-handling|Fehlerbehandlung – SafeAdd, Result, Option]]
* [[lyx_-_programmiersprache:do-178c|DO-178C – Range-Typen und Datensicherheit]]
* [[lyx_-_programmiersprache:das-energy-aware-programmiermodell|Energy-Aware – qbool und QBool-Operationen]]