====== Lyx – Pattern Matching ====== ''match'' ist das zentrale Kontrollfluss-Element für Fallunterscheidungen in Lyx. Es ist typsicher, **exhaustiv** — der Compiler prüft, ob alle möglichen Werte abgedeckt sind — und wird vom Backend oft zu einer Jump-Table optimiert, die schneller ist als eine if/else-Kette. Der Unterschied zu ''switch'' in C: Es gibt kein Fall-through, kein vergessenes ''break'', keine Sprünge zwischen Armen. Jeder Arm ist vollständig isoliert. Der Compiler warnt, wenn Fälle fehlen. → [[lyx_-_programmiersprache:sprache:exception-handling|Fehlerbehandlung]] · [[lyx_-_programmiersprache:sprache:bedingungen|Bedingungen]] · [[lyx_-_programmiersprache:sprache:datentypen|Datentypen]] ---- ===== 1. Grundsyntax ===== match (ausdruck) { case muster1 => anweisung; case muster2 => { anweisung1; anweisung2; } case _ => anweisung; } * Jeder Arm besteht aus ''case muster =>'' gefolgt von einer Anweisung oder einem Block. * ''case _ =>'' fängt alle Werte ab, die kein explizites ''case'' getroffen haben. Ein ''default''-Zweig wie in C/Java existiert nicht. * Kein Fall-through — nur der erste passende Arm wird ausgeführt. * Blöcke ''{ }'' sind optional bei einer einzelnen Anweisung, aber empfohlen. fn HttpStatusText(code: int64): pchar { match (code) { case 200 => { return "OK"; } case 201 => { return "Created"; } case 204 => { return "No Content"; } case 301 => { return "Moved Permanently"; } case 400 => { return "Bad Request"; } case 401 => { return "Unauthorized"; } case 403 => { return "Forbidden"; } case 404 => { return "Not Found"; } case 500 => { return "Internal Server Error"; } case 503 => { return "Service Unavailable"; } case _ => { return "Unknown"; } } } fn main(): int64 { PrintLn(HttpStatusText(404)); // "Not Found" PrintLn(HttpStatusText(200)); // "OK" return 0; } ---- > **Auf ''--target=lyxos'' wählt ''match'' keinen Zweig** — auch nicht den Auffangfall ''case _''. Das Programm übersetzt ohne Meldung, läuft durch und gibt aus dem ''match'' nichts aus ([[https://github.com/SEOLizer/LyX-Compiler/issues/1825|#1825]], offen). Auf ''linux'', ''arm64'' und ''riscv'' ist das Verhalten unauffällig. > > Wer für Lyx OS baut, benutzt bis dahin ''if''/''else if''-Ketten — die tragen dort seit ''lyxc 1.1.8C'' ([[https://github.com/SEOLizer/LyX-Compiler/issues/1757|#1757]]). ===== 2. OR-Muster ( | ) ===== Mehrere Werte können in einem einzigen Arm zusammengefasst werden: fn ClassifyChar(c: int64): pchar { match (c) { case 65 | 69 | 73 | 79 | 85 => { return "Vokal (groß)"; } case 97 | 101 | 105 | 111 | 117 => { return "Vokal (klein)"; } case 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 => return "Ziffer"; case 32 | 9 | 10 | 13 => { return "Whitespace"; } case _ => { return "Sonstiges"; } } } fn ClassifyHttpMethod(method: int64): int64 { match (method) { case HTTP_GET | HTTP_HEAD | HTTP_OPTIONS => { return METHOD_SAFE; } case HTTP_POST | HTTP_PUT | HTTP_PATCH => { return METHOD_MUTATING; } case HTTP_DELETE => { return METHOD_DESTRUCTIVE; } case _ => { return METHOD_UNKNOWN; } } } ---- ===== 3. Bereichsmuster ( .. ) ===== Wertebereiche werden mit ''von..bis'' angegeben — der Bereich ist auf beiden Seiten einschließend: fn AltitudeMode(altitude_m: int64): pchar { match (altitude_m) { case 0..500 => { return "Bodennaher Betrieb"; } case 501..3000 => { return "Platzrunde / Anflug"; } case 3001..10000 => { return "Steigflug"; } case 10001..13000 => { return "Reiseflug"; } case 13001.. => { return "Hochflug"; } case _ => { return "Ungültige Höhe"; } } } fn HttpStatusClass(code: int64): pchar { match (code) { case 100..199 => { return "Informational"; } case 200..299 => { return "Success"; } case 300..399 => { return "Redirection"; } case 400..499 => { return "Client Error"; } case 500..599 => { return "Server Error"; } case _ => { return "Unknown"; } } } fn main(): int64 { PrintLn(AltitudeMode(11500)); // "Reiseflug" PrintLn(HttpStatusClass(404)); // "Client Error" return 0; } OR-Muster und Bereiche können kombiniert werden: fn IsSpecialPort(port: int64): bool { match (port) { case 80 | 443 => { return true; } // HTTP(S) case 20 | 21 => { return true; } // FTP case 22 => { return true; } // SSH case 1..1023 => { return true; } // Privilegierte Ports case _ => { return false; } } } ---- ===== 4. Enum-Matching und Exhaustivität ===== Das stärkste Argument für ''match'' in Lyx: Bei Enums prüft der Compiler, ob **alle** Werte abgedeckt sind. Fehlt ein Arm, ist es ein Compiler-Fehler — kein Warning, kein Silent-Fail. enum FlightPhase { Preflight, Taxiing, Takeoff, Climbing, Cruise, Descending, Approach, Landing, Rollout } fn PhaseActions(phase: FlightPhase): void { match (phase) { case FlightPhase.Preflight => { RunChecklist(); } case FlightPhase.Taxiing => { EnableTaxiLights(); CheckFlaps(); } case FlightPhase.Takeoff => { SetTakeoffPower(); RetractFlaps(); } case FlightPhase.Climbing => { RetractGear(); SetClimbPower(); } case FlightPhase.Cruise => { SetCruisePower(); EngageAutopilot(); } case FlightPhase.Descending => { SetDescentRate(); } case FlightPhase.Approach => { ExtendFlaps(); ArmAutobrake(); } case FlightPhase.Landing => { ExtendGear(); SetLandingPower(); } case FlightPhase.Rollout => { ApplyBrakes(); ReverseThrust(); } // Kein 'case _' nötig — alle 9 Werte sind abgedeckt // Wird ein neuer Wert zum Enum hinzugefügt → Compiler-Fehler hier } } Wird ''FlightPhase.GoAround'' später hinzugefügt, erzeugt der Compiler sofort: error: non-exhaustive match — FlightPhase.GoAround not covered --> flight_ctrl.lyx:42:5 hint: add 'case FlightPhase.GoAround =>' or 'case _ =>' Das ist eine Sicherheitsgarantie: Neue Zustände können nicht unbehandelt durchrutschen. Die tatsächliche Meldung mit ''lyxc 1.1.14A'' lautet ''sema error: match does not cover variant 'GoAround' — add a case or a wildcard _ => { … }''. > **Der Cast wird geprüft** ([[https://github.com/SEOLizer/LyX-Compiler/issues/1884|#1884]], nachgemessen mit lyxc 1.2.2B): ''%%99 as Status%%'' bricht mit ''sema error: Cast auf Enum: Wert ausserhalb der Varianten'' ab. Frühere Fassungen ließen den Wert durch, und er fiel anschließend durch ein vollständiges ''match'' — auch am Auffangfall vorbei. ==== Enum mit Werten ==== enum SensorStatus { Ok = 0, Timeout = 1, OutOfRange = 2, HardwareFault = 3 } fn HandleSensorStatus(s: SensorStatus): void { match (s) { case SensorStatus.Ok => { /* normal */ } case SensorStatus.Timeout => { PrintLn("Sensor-Timeout — Wiederholungsversuch"); RetrySensor(); } case SensorStatus.OutOfRange => { PrintLn("Messwert außerhalb Plausibilitätsbereich"); FlagSensorData(); } case SensorStatus.HardwareFault => { PrintLn("Hardware-Defekt — Fallback aktivieren"); ActivateRedundantSensor(); } } } ---- ===== 5. Match als Ausdruck ===== ''match'' kann direkt als Ausdruck verwendet werden — der Wert des passenden Arms wird zurückgegeben: fn DayName(day: int64): pchar { return match (day) { case 1 => "Montag"; case 2 => "Dienstag"; case 3 => "Mittwoch"; case 4 => "Donnerstag"; case 5 => "Freitag"; case 6 => "Samstag"; case 7 => "Sonntag"; case _ => "Ungültig"; }; } fn DaysInMonth(month: int64, year: int64): int64 { return match (month) { case 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31; case 4 | 6 | 9 | 11 => 30; case 2 => DaysInFebruary(year); case _ => 0; }; } fn main(): int64 { PrintLn(DayName(3)); // Mittwoch PrintLn(IntToStr(DaysInMonth(2, 2024))); // 29 PrintLn(""); return 0; } ---- ===== 6. Guard-Bedingungen ===== Mit ''when'' kann einem Muster eine zusätzliche Bedingung hinzugefügt werden. Der Arm passt nur, wenn sowohl das Muster als auch die Guard-Bedingung wahr sind: fn ClassifyTemperature(sensor_id: int64, temp: f64): pchar { match (sensor_id) { case 1 if temp > 100.0 => { return "Sensor 1: Überhitzung"; } case 1 if temp < -20.0 => { return "Sensor 1: Untertemperatur"; } case 1 => { return "Sensor 1: Normal"; } case 2 if temp > 80.0 => { return "Sensor 2: Warnung"; } case 2 => { return "Sensor 2: Normal"; } case _ => { return "Unbekannter Sensor"; } } } fn RoutePacket(port: int64, size: int64): pchar { match (port) { case 80 if size > 65535 => { return "HTTP: Paket zu groß"; } case 80 => { return "HTTP-Handler"; } case 443 if size > 65535 => { return "HTTPS: Paket zu groß"; } case 443 => { return "HTTPS-Handler"; } case 1..1023 => { return "Privilegierter Port"; } case _ => { return "Unprivilegierter Port"; } } } ---- ===== 7. Struct-Deconstruction ===== Match kann direkt in Struct-Felder hineinschauen und auf deren Werte matchen: type Packet = struct { type_id: int64; length: int64; flags: int64; checksum: int64; }; fn ProcessPacket(pkt: Packet): void { match (pkt) { case Packet { type_id: 1, flags: 0 } => { PrintLn("Datenpunkt, keine Flags"); ProcessData(pkt); } case Packet { type_id: 1 } => { PrintLn("Datenpunkt mit Flags"); ProcessDataWithFlags(pkt); } case Packet { type_id: 2, length: 0..8 } => { PrintLn("Kurzbefehl"); ProcessShortCommand(pkt); } case Packet { type_id: 2 } => { PrintLn("Langer Befehl"); ProcessCommand(pkt); } case Packet { type_id: 255 } => { PrintLn("Heartbeat"); ResetWatchdog(); } case _ => { PrintLn("Unbekanntes Paket — verworfen"); } } } Felder, die nicht im Muster auftauchen, werden ignoriert — es ist kein Wildcard nötig. ---- ===== 8. Verschachteltes Matching ===== ''match'' kann in anderen ''match''-Armen verschachtelt werden — nützlich für mehrstufige Zustandsmaschinen: enum ProtocolState { Idle, Handshake, Active, Closing } enum ErrorCode { None, Timeout, AuthFailed, Overflow } fn HandleProtocolEvent(state: ProtocolState, error: ErrorCode): void { match (state) { case ProtocolState.Idle => { match (error) { case ErrorCode.None => StartHandshake(); case ErrorCode.Timeout => ResetConnection(); case _ => LogError(error); } } case ProtocolState.Handshake => { match (error) { case ErrorCode.None => TransitionToActive(); case ErrorCode.AuthFailed => RejectConnection(); case ErrorCode.Timeout => RetryHandshake(); case _ => AbortHandshake(); } } case ProtocolState.Active => { match (error) { case ErrorCode.None => { /* normal */ } case ErrorCode.Overflow => HandleOverflow(); case _ => GracefulClose(); } } case ProtocolState.Closing => { // Im Closing-Zustand alle Fehler ignorieren } } } ---- ===== 9. Performance ===== Der Compiler optimiert ''match'' je nach Struktur der Muster: ^ Muster-Struktur ^ Backend-Strategie ^ Eigenschaft ^ | Wenige dichte Einzelwerte (0,1,2,3) | Jump-Table | O(1) — schnellste Variante | | Viele verstreute Einzelwerte | Binäre Suche | O(log n) | | Bereiche | Vergleichsfolge | O(n) im Worst Case | | Struct-Deconstruction | Feldvergleiche | Abhängig von Feldanzahl | | Enum (vollständig) | Jump-Table oder direkt | O(1) — Enum-Werte sind int64 | # Assembler-Ausgabe prüfen — zeigt ob Jump-Table erzeugt wurde lyxc main.lyx --emit-asm | grep -A 20 "HttpStatusText" Ein ''match'' auf 10 dichte HTTP-Statuscodes erzeugt eine Jump-Table mit einem einzigen indirekten Sprung — ein ''if/else''-Äquivalent würde 10 Vergleiche brauchen. ---- ===== 10. match vs. if/else — Wann was ===== ^ Kriterium ^ match ^ if/else ^ | Viele Fälle auf denselben Wert | ✓ Klar strukturiert | ✗ Lange Kette | | Exhaustivitäts-Garantie | ✓ Compiler erzwingt | ✗ Manuell | | Enum-Abdeckung | ✓ Vollständigkeitsprüfung | ✗ Keine | | OR-Muster | ✓ ''case 1 %%|%% 2 %%|%% 3 =>'' | ○ Umständlich | | Bereichsprüfungen | ✓ ''case 100..199 =>'' | ✓ ''if''-Kette | | Komplexe boolesche Logik | ✗ Ungeeignet | ✓ Natürlicher | | Struct-Felder prüfen | ✓ Deconstruction | ○ Einzelne if-Abfragen | | Zwei-Wege-Entscheidung | ○ Overkill | ✓ Natürlicher | **Faustregel:** Ab drei Fällen auf denselben Ausdruck ist ''match'' klarer. Bei Enums immer ''match''. ---- ===== 11. Pattern Matching in Safety-Code ===== In DO-178C-zertifiziertem Code ist ''match'' besonders wertvoll, weil es Exhaustivität erzwingt. Fehlende Zustände können in sicherheitskritischen Systemen fatale Folgen haben. Zwei Grenzen sind dabei zu kennen: ein ''as''-Cast erzeugt Werte außerhalb der Aufzählung ([[https://github.com/SEOLizer/LyX-Compiler/issues/1884|#1884]]), und Enums sind untereinander nicht typgetrennt ([[https://github.com/SEOLizer/LyX-Compiler/issues/1883|#1883]]) — zwei Automaten mit gleichnamigen Zuständen lassen sich verwechseln. enum SystemMode { Initializing, Standby, Active, Degraded, // Eingeschränkter Betrieb wegen Sensorausfall Emergency, Shutdown } @dal(B) @flight_crit @stack_limit(256) fn ApplySystemMode(mode: SystemMode): void { match (mode) { case SystemMode.Initializing => { DisableAllOutputs(); RunSelfTest(); } case SystemMode.Standby => { SetIdleConsumption(); EnableWakeupSensor(); } case SystemMode.Active => { EnableFullOperation(); StartControlLoop(); } case SystemMode.Degraded => { DisableNonCriticalSystems(); ActivateRedundantSensors(); AlertCrew(); } case SystemMode.Emergency => { ActivateSafeMode(); TriggerEmergencyProtocol(); AlertCrew(); } case SystemMode.Shutdown => { SaveFlightData(); DisableAllOutputs(); } // Kein default — jeder neue Modus erzeugt sofort einen Compiler-Fehler // Das erzwingt, dass neue Zustände immer bewusst behandelt werden } } ==== Exhaustivität als Sicherheitsnetz ==== Wird das System um einen neuen Modus erweitert — z.B. ''Maintenance'' — und ''ApplySystemMode'' nicht aktualisiert, bricht der Build sofort ab: error: non-exhaustive match — SystemMode.Maintenance not covered --> flight_ctrl.lyx:88:5 in function: ApplySystemMode hint: add 'case SystemMode.Maintenance =>' or 'case _ =>' Dieser Fehler tritt beim Build auf — nicht zur Laufzeit in einem Flugzeug. ---- ===== 12. Vollständiges Beispiel: Protokoll-Parser ===== Ein binärer Protokoll-Parser, der ''match'' auf mehreren Ebenen nutzt: unit FrameParser; enum FrameType { Data = 0x01, Command = 0x02, Ack = 0x03, Nack = 0x04, Heartbeat = 0xFF } enum CommandId { Reset = 0x01, Configure = 0x02, Calibrate = 0x03, Shutdown = 0x0F } type Frame = struct { frame_type: int64; command_id: int64; length: int64; payload: int64; checksum: int64; }; fn ParseFrame(raw: int64, len: int64): Frame { // ... Frame aus Bytes aufbauen ... var _r: Frame; _r.frame_type := 0; _r.command_id := 0; _r.length := 0; _r.payload := 0; _r.checksum := 0; return _r; } fn ValidateChecksum(frame: Frame): bool { return true; // vereinfacht } pub fn ProcessFrame(raw: int64, len: int64): void { if (len < 5) { PrintLn("Frame zu kurz"); return; } var frame: Frame := ParseFrame(raw, len); if (!ValidateChecksum(frame)) { PrintLn("Checksummen-Fehler"); return; } match (frame.frame_type) { case FrameType.Data => { PrintLn(StrConcat(StrConcat("Datenframe: ", IntToStr(frame.length)), " Bytes")); StoreData(frame.payload, frame.length); } case FrameType.Command => { match (frame.command_id) { case CommandId.Reset => { PrintLn("Reset-Kommando"); PerformReset(); } case CommandId.Configure => { PrintLn("Konfiguration"); ApplyConfig(frame.payload, frame.length); } case CommandId.Calibrate => { PrintLn("Kalibrierung"); RunCalibration(); } case CommandId.Shutdown => { PrintLn("Shutdown-Kommando"); InitiateShutdown(); } // Alle Kommandos abgedeckt — Compiler schlägt Alarm bei Erweiterung } } case FrameType.Ack => { PrintLn("ACK empfangen"); ClearRetryBuffer(); } case FrameType.Nack => { PrintLn("NACK empfangen — Wiederholung"); RetransmitLastFrame(); } case FrameType.Heartbeat => { ResetWatchdog(); } // Alle FrameType-Werte abgedeckt } } ---- ===== Best Practices ===== Empfehlungen für den produktiven Einsatz: ^ Situation ^ Empfehlung ^ | Enum-Fallunterscheidung | Immer ''match'' — kein ''if/else'' | | Kein ''default'' bei Enums | Exhaustivität als Sicherheitsnetz nutzen | | Viele dichte Einzelwerte | ''match'' für Jump-Table-Optimierung | | Bereiche | ''case lo..hi =>'' — beide Grenzen einschließend; offene Form ''case lo.. =>'' möglich | | Mehrere Werte, gleiche Aktion | ''case a %%|%% b %%|%% c =>'' | | Komplexe Nebenbedingungen | Guard mit ''when'' | | Neue Zustände/Kommandos | Build-Fehler durch Exhaustivität — Feature, kein Bug | | Safety-Code (DAL-A/B) | Kein ''default'' bei zustandsbehafteten Enums | ---- ---- ===== Häufiger Fallstrick: ''match'' ist ein reserviertes Keyword ===== ''match'' ist ein echtes Lyx-Schlüsselwort (''TK_MATCH = 27'') und darf **nicht** als Variablenname verwendet werden. Der Compiler gibt bei ''var match: ...'' einen kryptischen Parse-Fehler aus, der nicht auf das Namenskonflikt-Problem hinweist. // FALSCH — Compile-Fehler (kryptisch, nicht selbsterklärend): var match: int64 := StrFind(s, pattern); // RICHTIG: var matchPos: int64 := StrFind(s, pattern); var found: int64 := StrFind(s, pattern); var idx: int64 := StrFind(s, pattern); Weitere reservierte Keywords, die als Variablennamen ungeeignet sind: ''if'', ''else'', ''while'', ''for'', ''fn'', ''pub'', ''con'', ''var'', ''return'', ''import'', ''unit'', ''type'', ''true'', ''false'', ''null''. ---- → [[lyx_-_programmiersprache:sprache:exception-handling|Fehlerbehandlung — Result, Tuple-Return, panic()]]\\ → [[lyx_-_programmiersprache:sprache:bedingungen|Bedingungen & Logik — if, while, for]]\\ → [[lyx_-_programmiersprache:sprache:enums|Enums — vollständige Referenz]]\\ → [[lyx_-_programmiersprache:sprache:datentypen|Datentypen — Enums, Structs, Skalare]]\\ → [[lyx_-_programmiersprache:guides:do-178c|DO-178C — Sicherheitskritische Programmierung]] Letzte Aktualisierung: 2026-09-05 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1884|#1884]], nachgemessen mit lyxc 1.2.2B) — Kasten gezogen: ein Enum-Cast außerhalb der Varianten wird abgewiesen. Vorherige letzte Aktualisierung: 2026-08-27 — Hinweis zu ''match'' auf ''--target=lyxos'' ergänzt (#1825), mit ''lyxc 1.1.11B'' erhoben. 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). Letzte Aktualisierung: 2026-08-30 — Exhaustivitätsprüfung gegen ''lyxc 1.1.14A'' nachgemessen (Meldungstext berichtigt) und ihre Grenze beim ''as''-Cast ergänzt (#1884, #1883).