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.
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");
}
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;
| 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 {
// ...
}
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;
| 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, nicht Zuweisung!)
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: 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
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 |
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: Pointer & Inlining – vollständige Referenz
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.
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);
PrintInt(list.len()); // 7
PrintInt(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 signal: parallel Array<f32>(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<T> | Heap | Dynamisch | ✅ Ja | ❌ Verboten |
parallel Array<T> | Heap (SIMD-aligned) | Dynamisch | ✅ Ja | ❌ Verboten |
Map<K, V> ist eine Hash-Map mit typisiertem Schlüssel und Wert. Literale werden mit {key: value}-Syntax geschrieben.
var scores: Map<pchar, int64> := {
"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
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
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;
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) | ❌ |
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 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
@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.
</code>
===== 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:
int32 statt int64)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:
–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.
===== 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: 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<T> | Heap | – |
| SIMD-Array | parallel Array<T> | Heap (aligned) | – |
| Assoziativ | Map<K,V> | 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<T> dynamische Arrays | ✅ Vollständig |
parallel Array<T> (SIMD) | ✅ Vollständig |
Map<K,V> 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: