====== Lyx – Arrays, Maps & Datensammlungen ====== Lyx unterscheidet vier Container-Formen. Die Wahl zwischen ihnen ist keine Stilfrage, sondern eine Sicherheits- und Architekturentscheidung: Stack-Arrays sind deterministisch und DO-178C-tauglich, Heap-Arrays sind flexibel aber nicht echtzeitfähig, SIMD-Arrays für numerische Hochleistungsberechnungen, Maps für Schlüssel-Wert-Zugriff. > **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''. → [[lyx_-_programmiersprache:sprache:datentypen|Datentypen]] · [[lyx_-_programmiersprache:sprache:memory-management|Memory Management]] · [[lyx_-_programmiersprache:guides:do-178c|DO-178C]] ---- ===== 1. Statische Arrays — [N]T ===== Statische Arrays liegen auf dem **Stack**. Größe und Typ stehen zur Compile-Zeit fest und sind unveränderlich. Das ist die bevorzugte Form für sicherheitskritischen und echtzeitfähigen Code. ==== Syntax ==== // [Größe]Typ — Typ-zuerst-Notation (wie Go/Zig, NICHT wie C) var buffer: [256]uint8; // ohne Initialisierer var coords: [3]f64 := [0.0, 0.0, 0.0]; var ids: [16]int64; // let — unveränderliche Bindung (Werte im Array selbst bleiben änderbar) let defaults: [4]int64 := [0, 1, 2, 3]; > **Wichtig:** Die Syntax ist ''[N]T'' — Größe in eckigen Klammern **vor** dem Typ. ''T[N]'' (C-Stil) ist in Lyx kein gültiger Typ. ==== Zugriff und Zuweisung ==== var coords: [3]f64 := [0.0, 0.0, 0.0]; coords[0] := 10.5; // Schreiben coords[1] := coords[0]; // Lesen und Schreiben var z: f64 := coords[2]; // Lesen // Bounds-Checking: // — Konstanter Index außerhalb der Grenzen → Compile-Fehler // — Variabler Index außerhalb der Grenzen → panic, aber nur mit --runtime-checks > **Die Laufzeitprüfung hängt am Schalter ''--runtime-checks''.** Ohne ihn liest ein Zugriff außerhalb der Grenzen kommentarlos fremden Speicher: ''a[9]'' auf einem ''[4]int64'' liefert einen beliebigen Wert statt eines Abbruchs. Mit dem Schalter meldet das Programm ''index out of bounds'' und endet mit Exit 1. Für sicherheitskritische Builds gehört er damit zur Pflichtausstattung. ==== Mehrdimensionale statische Arrays ==== > **Mehrdimensionale Arrays tragen seit lyxc 1.0.17C** (#1230). ''%%var m: [3][4]int64;%%'' lässt sich lesen und schreiben (''m[1][2] := 7''). Umgesetzt ist **genau eine** Verschachtelung: die Daten liegen flach (N*M Slots hinter einem ''{cap,len}''-Kopf), ''m[i]'' liefert die Adresse der Zeile, ''m[i][j]'' liest von dort. Eine ganze Zeile zuzuweisen (''m[i] := x'') und eine dritte Dimension werden abgewiesen. Bis 1.0.16x endete **jeder** Zugriff mit Exit 139. ==== Safety-Eigenschaften ==== * Keine Heap-Allokation — kein Fragmentierungsrisiko, kein GC-Druck * Bounds-Checking bei konstantem Index zur Compile-Zeit * Bounds-Checking bei variablem Index zur Laufzeit — **nur mit ''--runtime-checks''** (kontrollierter ''panic'') * Maximaler Stack-Verbrauch statisch berechenbar → kompatibel mit ''@stack_limit'' * Kein ''dispose'' nötig — Stack-Frame wird automatisch freigegeben // DO-178C DAL-A: Waypoint-Liste als statisches Array @dal(A) @flight_crit @stack_limit(2048) @wcet(100) fn FindNearest(wps: [32]Waypoint, n: int64, pos: GeoPoint): int64 { var best: int64 := 0; var best_d: f64 := 1.0e18; for i := 0 to n - 1 do { var d: f64 := GeoDistance(pos, wps[i].pos); if (d < best_d) { best_d := d; best := i; } } return best; } ---- ===== 2. Dynamische Arrays — array ===== Dynamische Arrays liegen auf dem **Heap** und wachsen automatisch. Intern speichern sie drei Werte (Fat-Pointer): Speicheradresse, aktuelle Länge (''len'') und reservierte Kapazität (''cap''). ==== Syntax und Operationen ==== import std.io; var list: array := [10, 20, 30]; PrintLn(IntToStr(len(list))); // 3 — len() ist ein Builtin, keine Methode PrintLn(IntToStr(list[0])); // 10 list[0] := list[0] + 1; // Lesen und Schreiben list[2] := 999; list.push(40); // Anhängen var letzter: int64 := list.pop(); // Entnehmen — liefert 40 dispose list; // Zwingend — sonst Memory Leak Eine blosse Deklaration ohne ''%%:=%%'' legt seit lyxc 1.0.13 ein leeres Array an — ''push'' darauf funktioniert, und ''len()'' zählt korrekt mit: var u: array; // ohne Initialisierer u.push(5); u.push(7); PrintLn(IntToStr(len(u))); // 2 PrintLn(IntToStr(u[0])); // 5 Dasselbe gilt für ein Array aus einem Literal: ''len()'' folgt seit lyxc 1.0.15B auch dort dem ''push''. ''len()'' auf einem **leeren** Array-Literal liefert ''0'', ohne dass vorher ein ''push'' nötig wäre. Bis lyxc 1.1.2H legte ''%%:= []%%'' keinen Kopf an und ''len()'' las an Offset 0 eines Nullzeigers — Speicherzugriffsfehler in genau der Zeile, mit der jede einsammelnde Schleife beginnt ([[https://github.com/SEOLizer/LyX-Compiler/issues/1642|#1642]]). import std.io; fn main(): int64 { var a: array := []; PrintInt(len(a)); // 0 return 0; } var l := [1, 2, 3]; push(l, 4); PrintLn(IntToStr(len(l))); // 4 Die Bereichsprüfung zur Compile-Zeit entfällt für eine solche Variable, sobald sie im selben Rumpf wächst — erkannt an ''push'', ''append'' oder ''pop'', in freier Form (''%%push(a, x)%%'') wie in Methodenform (''%%a.push(x)%%''). Nur bei einer Deklaration mit fester Größe (''%%[N]T%%'') bleibt die vermerkte Größe die Schranke. > **Dynamische Arrays taugen seit lyxc 1.0.17C als Funktionsparameter** (#1197). ''fn F(a: array<int64>): int64 { return a[0]; }'' liefert den Elementwert; bis 1.0.16x kam dort eine Adresse an, also eine wechselnde Zahl. Dasselbe galt für Feldzugriffe und Verkettungen — auch die liefern jetzt Werte statt Adressen. ==== Felder eines Arrays ==== An einem Array sind genau vier Feldzugriffe gültig: ^ Zugriff ^ Bedeutung ^ | ''a.length'', ''a.len'' | Anzahl der Elemente | | ''a.cap'' | Kapazität | | ''a.data'' | Zeiger auf das erste Element (mit ''peek64'' lesbar) | Jeder andere Feldname wird seit lyxc 1.0.15B mit ''unknown field'' abgewiesen — ein Tippfehler wie ''list.laenge'' ist damit nicht mehr von einem gültigen Feld zu unterscheiden, sondern ein Übersetzungsfehler. Dasselbe gilt an eingebauten Grundtypen: ''%%n.egal%%'' an einem ''int64'' meldet ''unknown field''. > **''list.len()'', ''list.cap()'' und ''list.clear()'' als //Methoden// gibt es weiterhin nicht** — der Aufruf meldet ''undefined function''. Die Länge liefert das Builtin ''len(list)'' oder das Feld ''list.len''. ==== dispose mit mehreren return-Pfaden ==== Verlässt eine Funktion über mehrere Pfade, muss ''dispose'' **vor jedem** ''return'' stehen: fn LoadConfig(path: pchar): bool { var cfg: Map := {}; if (ParseFile(path, cfg) == false) { dispose cfg; // Auch im Fehlerfall freigeben return false; } ApplyConfig(cfg); dispose cfg; return true; } > Lyx hat kein automatisches RAII/Destruktor-System für Heap-Typen. Bei mehreren ''return''-Pfaden statische Arrays ''[N]T'' bevorzugen — kein ''dispose'', kein Leak-Risiko. ==== Wann array sinnvoll ist ==== * Anzahl der Elemente zur Compile-Zeit unbekannt * Server- und Tool-Code, bei dem Flexibilität wichtiger ist als deterministisches Timing * **Nicht** in DAL-A/B-Regelschleifen — Heap-Allokation hat nicht-deterministische Latenz > In Funktionen mit ''@flight_crit'' warnt der Compiler bei Heap-Allokation. Ausschließlich ''[N]T'' verwenden. ---- ===== 3. Parallele Arrays — parallel Array ===== Parallele Arrays sind Heap-Arrays mit garantiertem **SIMD-Alignment** (16 Byte für SSE/NEON, 32 Byte für AVX). Batch-Operationen auf diesen Arrays übersetzt der Compiler direkt in Vektor-Instruktionen. var data: parallel Array := parallel Array(1024); data[0] := 1.0; data[1] := 2.0; // Batch-Skalierung — compiler übersetzt in SSE/AVX/NEON // data := data * 2.0; dispose data; ==== Einsatzgebiete ==== * Signalverarbeitung (FFT, Filter, Fensterfunktionen) * Lineare Algebra (Matrix-Vektor-Produkt) * Machine-Learning-Inferenz (Dot-Produkte, Aktivierungen) * Physik-Simulationen (Partikelsysteme, Kraftfelder) * Nur für primitive Typen: ''f32'', ''f64'', ''int32'', ''int64'' * Kein Einsatz in DAL-A/B-Regelschleifen — SIMD-Timing nicht vorhersehbar genug ---- ===== 4. Maps — Map ===== Maps erlauben den Zugriff über einen Schlüssel. Lyx bietet native Literal-Syntax seit v0.5.0; Lesen und Schreiben über ganzzahlige Schlüssel sind seit [[https://github.com/SEOLizer/LyX-Compiler/issues/1152|#1152]] umgesetzt und mit ''lyxc 1.1.11B'' nachgemessen (''%%m[3] := 7%%'' → ''%%m[3]%%'' ergibt ''7''). Eine Iteration über Schlüssel-Wert-Paare gibt es weiterhin nicht. > **Nur ganzzahlige Schlüssel.** Die Laufzeit vergleicht den Schlüssel als Zahl. Bei ''pchar'' wäre das die //Adresse// — zwei gleich geschriebene Literale haben verschiedene Adressen, ''%%m["Alpha"]%%'' fände seinen eigenen Eintrag also nicht wieder. ''Map<pchar, V>'' wird deshalb bereits bei der Deklaration abgewiesen, statt still danebenzugreifen. Für Zeichenketten-Schlüssel den Schlüssel selbst hashen (''[[lyx_-_programmiersprache:units:hash|std.hash]]'') und den Hash als ganzzahligen Schlüssel führen. (Inhaltsvergleich ist als [[https://github.com/SEOLizer/LyX-Compiler/issues/1291|Issue #1291]] aufgesetzt.) ==== Erstellen, Lesen, Schreiben ==== import std.io; var scores: Map := {1: 100, 2: 250, 3: 175}; // Lesen — ein fehlender Schlüssel liefert 0 var s: int64 := scores[1]; // 100 // Schreiben / Einfügen scores[4] := 300; // Existenzprüfung mit in — immer vor Zugriff auf unbekannte Schlüssel if (4 in scores) { PrintLn(IntToStr(scores[4])); // 300 } // Anzahl der Einträge PrintLn(IntToStr(len(scores))); // 4 dispose scores; Eine Deklaration ohne Initialisierer legt eine leere, sofort benutzbare Map an: var m: Map; m[1] := 5; PrintLn(IntToStr(len(m))); // 1 > **''delete m[k]'' und ''for key, val in m do'' gibt es nicht.** Beide sind im Parser nicht umgesetzt; ''delete'' meldet ''undefined symbol''. Einen Eintrag „entfernt" man ersatzweise durch Überschreiben mit einem Sentinel-Wert, und iteriert über die Schlüssel, indem man sie zusätzlich in einem ''array<int64>'' mitführt. > ([[https://github.com/SEOLizer/LyX-Compiler/issues/1152|Issue #1152]]) ==== Performance-Modell ==== Die Laufzeit ist eine Hashtabelle mit **offener Adressierung**: Sondiert wird ab ''key & (cap-1)''. Zugriff damit O(1) amortisiert, unabhängig von der Anzahl der Einträge. ---- ===== 5. Speicherverwaltung — Heap-Container ===== Alle Heap-Container (''array<T>'', ''parallel Array<T>'', ''Map<K,V>'') müssen explizit freigegeben werden: fn ProcessData(): void { var buf: array := []; var data: parallel Array := parallel Array(256); var meta: Map := {}; // ... Verarbeitung ... dispose buf; dispose data; dispose meta; } Wird ''dispose'' vergessen, entsteht ein Memory Leak — besonders in Langzeit-Systemen (Avionik, Server) kritisch. ---- ===== 6. Zusammenfassung & Safety-Matrix ===== ^ Typ ^ Syntax ^ Ort ^ Timing ^ DO-178C DAL-A ^ dispose ^ | Statisches Array | ''[N]T'' | Stack | Deterministisch | ✅ | Nein | | Dynamisches Array | ''array<T>'' | Heap | Nicht-deterministisch | ✗ (nur Init) | Ja | | Paralleles Array | ''parallel Array<T>'' | Heap (SIMD) | Nicht-deterministisch | ✗ | Ja | | Map | ''Map<K, V>'' | Heap | Nicht-deterministisch | ✗ | Ja | ^ Eigenschaft ^ ''[N]T'' ^ ''array<T>'' ^ ''parallel Array<T>'' ^ ''Map<K,V>'' ^ | Größe zur Compile-Zeit | Ja | Nein | Nein | Nein | | Bounds-Checking | Compile + Runtime | Runtime | Runtime | Runtime | | @stack_limit-kompatibel | Ja | Nein | Nein | Nein | | SIMD-Vektorisierung | Nein | Nein | Ja | Nein | | Schlüssel-Zugriff | Nein | Nein | Nein | Ja — nur ganzzahlige Schlüssel | | for key, val in | Nein | Nein | Nein | Nein — ''for key, val in m'' endet mit ''Parse error: expected :=, got ','' (mit 1.1.11B nachgemessen) | Eine Iteration über die **Elemente** eines Arrays (''for v in a do'') gibt es nicht — der Versuch meldet ''for ... in erwartet einen Bereich''. Über ein Array läuft man mit Index: for i := 0; i < len(a); i++ { PrintLn(IntToStr(a[i])); } Für Zählschleifen stehen daneben ''for i := 0 to N'', ''for i in range(N)'' und seit lyxc 1.0.14 auch ''for i in 0..N'' zur Verfügung — letzteres mit **einschließender** Obergrenze. ---- Letzte Aktualisierung: 2026-08-19 — ''len()'' auf dem leeren Array-Literal ([[https://github.com/SEOLizer/LyX-Compiler/issues/1642|#1642]]) ergänzt; Beispiel mit ''lyxc 1.1.3I'' übersetzt und ausgeführt. Vorherige Aktualisierung: 2026-08-11 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).