====== 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:exception-handling|Fehlerbehandlung]] · [[lyx_-_programmiersprache:dedingungen|Bedingungen]] · [[lyx_-_programmiersprache:datentypen|Datentypen]] ---- ===== 1. Grundsyntax ===== match (ausdruck) { case muster1 => anweisung; case muster2 => { anweisung1; anweisung2; } default => anweisung; } * Jeder Arm besteht aus ''case muster =>'' gefolgt von einer Anweisung oder einem Block. * ''default'' fängt alle Werte ab, die kein explizites ''case'' getroffen haben. * 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"; default => return "Unknown"; } } fn main(): int64 { PrintStr(HttpStatusText(404)); // "Not Found" PrintStr("\n"); PrintStr(HttpStatusText(200)); // "OK" PrintStr("\n"); return 0; } ---- ===== 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"; default => 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; default => 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"; default => 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"; default => return "Unknown"; } } fn main(): int64 { PrintStr(AltitudeMode(11500)); // "Reiseflug" PrintStr("\n"); PrintStr(HttpStatusClass(404)); // "Client Error" PrintStr("\n"); 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 default => 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 'default' 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 'default =>' Das ist eine Sicherheitsgarantie: Neue Zustände können nicht unbehandelt durchrutschen. ==== 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 => { PrintStr("Sensor-Timeout — Wiederholungsversuch\n"); RetrySensor(); } case SensorStatus::OutOfRange => { PrintStr("Messwert außerhalb Plausibilitätsbereich\n"); FlagSensorData(); } case SensorStatus::HardwareFault => { PrintStr("Hardware-Defekt — Fallback aktivieren\n"); 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"; default => "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 => IsLeapYear(year) ? 29 : 28; default => 0; }; } fn main(): int64 { PrintStr(DayName(3)); // Mittwoch PrintStr("\n"); PrintInt(DaysInMonth(2, 2024)); // 29 PrintStr("\n"); 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 when temp > 100.0 => return "Sensor 1: Überhitzung"; case 1 when temp < -20.0 => return "Sensor 1: Untertemperatur"; case 1 => return "Sensor 1: Normal"; case 2 when temp > 80.0 => return "Sensor 2: Warnung"; case 2 => return "Sensor 2: Normal"; default => return "Unbekannter Sensor"; } } fn RoutePacket(port: int64, size: int64): pchar { match (port) { case 80 when size > 65535 => return "HTTP: Paket zu groß"; case 80 => return "HTTP-Handler"; case 443 when size > 65535 => return "HTTPS: Paket zu groß"; case 443 => return "HTTPS-Handler"; case 1..1023 => return "Privilegierter Port"; default => 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 } => { PrintStr("Datenpunkt, keine Flags\n"); ProcessData(pkt); } case Packet { type_id: 1 } => { PrintStr("Datenpunkt mit Flags\n"); ProcessDataWithFlags(pkt); } case Packet { type_id: 2, length: 0..8 } => { PrintStr("Kurzbefehl\n"); ProcessShortCommand(pkt); } case Packet { type_id: 2 } => { PrintStr("Langer Befehl\n"); ProcessCommand(pkt); } case Packet { type_id: 255 } => { PrintStr("Heartbeat\n"); ResetWatchdog(); } default => { PrintStr("Unbekanntes Paket — verworfen\n"); } } } 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(); default => LogError(error); } } case ProtocolState::Handshake => { match (error) { case ErrorCode::None => TransitionToActive(); case ErrorCode::AuthFailed => RejectConnection(); case ErrorCode::Timeout => RetryHandshake(); default => AbortHandshake(); } } case ProtocolState::Active => { match (error) { case ErrorCode::None => { /* normal */ } case ErrorCode::Overflow => HandleOverflow(); default => 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 =>'' | ○ Möglich, aber unübersichtlich | | 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. 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 'default =>' 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 ... return Frame { frame_type: 0, command_id: 0, length: 0, payload: 0, checksum: 0 }; } fn ValidateChecksum(frame: Frame): bool { return true; // vereinfacht } pub fn ProcessFrame(raw: int64, len: int64): void { if (len < 5) { PrintStr("Frame zu kurz\n"); return; } var frame: Frame := ParseFrame(raw, len); if (!ValidateChecksum(frame)) { PrintStr("Checksummen-Fehler\n"); return; } match (frame.frame_type) { case FrameType::Data => { PrintStr("Datenframe: "); PrintInt(frame.length); PrintStr(" Bytes\n"); StoreData(frame.payload, frame.length); } case FrameType::Command => { match (frame.command_id) { case CommandId::Reset => { PrintStr("Reset-Kommando\n"); PerformReset(); } case CommandId::Configure => { PrintStr("Konfiguration\n"); ApplyConfig(frame.payload, frame.length); } case CommandId::Calibrate => { PrintStr("Kalibrierung\n"); RunCalibration(); } case CommandId::Shutdown => { PrintStr("Shutdown-Kommando\n"); InitiateShutdown(); } // Alle Kommandos abgedeckt — Compiler schlägt Alarm bei Erweiterung } } case FrameType::Ack => { PrintStr("ACK empfangen\n"); ClearRetryBuffer(); } case FrameType::Nack => { PrintStr("NACK empfangen — Wiederholung\n"); RetransmitLastFrame(); } case FrameType::Heartbeat => { ResetWatchdog(); } // Alle FrameType-Werte abgedeckt } } ---- ===== Best Practices ===== ^ 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 =>'' statt ''x >= lo && x <= hi'' | | 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 | ---- → [[lyx_-_programmiersprache:exception-handling|Fehlerbehandlung — Result, Tuple-Return, panic()]]\\ → [[lyx_-_programmiersprache:dedingungen|Bedingungen & Logik — if, while, for]]\\ → [[lyx_-_programmiersprache:datentypen|Datentypen — Enums, Structs, Skalare]]\\ → [[lyx_-_programmiersprache:do-178c|DO-178C — Sicherheitskritische Programmierung]] Letzte Aktualisierung: 2026-05-22