Inhaltsverzeichnis

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.

Fehlerbehandlung · Bedingungen · Datentypen


1. Grundsyntax

match (ausdruck) {
    case muster1 => anweisung;
    case muster2 => { anweisung1; anweisung2; }
    case _       => anweisung;
}

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 (#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 (#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 (#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 (#1884), und Enums sind untereinander nicht typgetrennt (#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.


Fehlerbehandlung — Result, Tuple-Return, panic()
Bedingungen & Logik — if, while, for
Enums — vollständige Referenz
Datentypen — Enums, Structs, Skalare
DO-178C — Sicherheitskritische Programmierung

Letzte Aktualisierung: 2026-09-05 (#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).