Inhaltsverzeichnis

Lyx – Bedingungen (if / else / match)

Bedingungen steuern den Kontrollfluss in Lyx. Sie erlauben es, Codeblöcke nur dann auszuführen, wenn eine bestimmte logische Voraussetzung erfüllt ist.

Schleifen · Pattern Matching · Sprachsyntax


1. Grundlegende Syntax

Eine if-Bedingung erwartet einen Ausdruck, der zu bool evaluiert.

if (altitude < 1000) {
    ActivateLandingGear();
} else if (altitude > 40000) {
    ReduceThrust();
} else {
    MaintainFlight();
}

var speed: int64 := 250;

if (speed == 250) {
    PrintLn("Reisegeschwindigkeit");
}

// Ungleich
if (speed != 0) {
    PrintLn("Flugzeug bewegt sich");
}

// Kleiner/größer
if (speed > 300 | speed < 50) {
    PrintLn("Außerhalb Normalbereich");
}


2. Logische Operatoren

Operator Bedeutung Auswertung Beispiel
&& UND (AND) Kurzschluss a > 0 && b > 0
|| ODER (OR) Kurzschluss a == 0 || b == 0
& UND (AND) beide Seiten a > 0 & b > 0
| ODER (OR) beide Seiten a == 0 | b == 0
! NICHT (NOT) !(a > 0)
!= Ungleich a != b

Kurzschlussauswertung: && und || werten die rechte Seite nur aus, wenn das Ergebnis noch nicht feststeht — && bricht bei falscher linker Seite ab, || bei wahrer. Das Ergebnis ist immer auf 0/1 normiert.

Damit funktioniert das Null-Guard-Idiom:

if (p != 0 && deref(p) > 0) { ... }   // deref(p) läuft nur wenn p != 0

& und | werten dagegen immer beide Seiten aus. Sie sind die richtige Wahl für Bitmaskierung und Integer-Flags, nicht für Bedingungen mit Seiteneffekten oder Null-Prüfungen.

import std.io;

fn Check(x: int64, y: int64): void {
    if (x > 0 && y > 0) {
        PrintLn("beide positiv");
    }

    if (x < 0 || y < 0) {
        PrintLn("mindestens eines negativ");
    }

    if (x != y) {
        PrintLn("ungleich");
    }
}

fn main(): int64 {
    Check(3, 5);
    Check(-1, 2);
    Check(4, 4);
    return 0;
}

Prüfung auf null

Lyx verwendet null (nicht nil) für den Abwesenheitswert. Adressen werden als int64 geführt — einen Pointer-Typ gibt es nicht (siehe Syntax-Referenz, Abschnitt 4):

// Optionaler Sensor — 0 bedeutet "nicht vorhanden"
var sensor: int64 := 0;

// Erst prüfen, dann zugreifen
if (sensor != 0) {
    ProcessData(peek64(sensor));
}

// Null-Koaleszenz-Operator
var safe_sensor: int64 := sensor ?? DefaultSensor();

In DAL-A/B-Code sollten optionale Adressen durch typsichere result-Rückgaben ersetzt werden — Null-Prüfungen sind fehleranfällig und schwer zu analysieren.

3. Fallunterscheidung mit Rückgabewert

Ein if als Ausdruck gibt es nicht — return if (x < 0) { -x } else { x }; ist ein Syntaxfehler, ebenso der ternäre Operator cond ? a : b. Stattdessen return in beiden Zweigen oder match:

fn Abs(x: int64): int64 {
    if (x < 0) { return -x; }
    return x;
}

fn ClampLabel(v: int64): pchar {
    if (v < 0)  { return "negativ"; }
    if (v == 0) { return "null"; }
    return "positiv";
}

match ist dagegen als Ausdruck verwendbar und oft die kompaktere Form:

fn DayName(day: int64): pchar {
    return match (day) {
        case 1 => "Montag";
        case 2 => "Dienstag";
        case _ => "Ungültig";
    };
}


4. Probabilistische Entscheidungen

Einen eingebauten Typ qbool und Literale wie 0.01q gibt es nicht. Probabilistische Logik läuft über die Unit std.qbool und deren Struct-Typ QBool; die Auswertung zu einem echten bool erfolgt mit Observe():

import std.qbool;

var failure: QBool := QBoolNew(0.01);   // 1 % Ausfallwahrscheinlichkeit

if (Observe(failure)) {
    // wird mit ~1 % Wahrscheinlichkeit ausgefuehrt
    TriggerEmergencyRoutine();
}

QBool ist kein Ersatz für normale Bedingungen in Safety-kritischem Code — es ist ein Werkzeug für probabilistische Modellierung.

→ Details: std.qbool · Datentypen, Abschnitt 6


5. Pattern Matching (match)

Für Enum-Fallunterscheidungen und strukturierte Typen bietet Lyx match als typsichere Alternative zu langen if/else if-Ketten.

match (flight_state) {
    case State.Idle    => { PowerOn(); }
    case State.Taxiing => { SetFlaps(10); }
    case State.InAir   => { RetractGear(); }
    case _             => { LogError(); }
}

 
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).

→ Vollständige Dokumentation: Pattern Matching


6. switch

Neben match gibt es switch. Der Unterschied: switch vergleicht gegen Konstantenausdrücke, kennt einen default-Zweig und ist eine Anweisung — kein Ausdruck. Jeder Zweig ist ein Block.

import std.io;

fn main(): int64 {
    var x: int64 := 2;

    switch (x) {
        case 1: { PrintLn("eins"); break; }
        case 2: { PrintLn("zwei"); break; }
        default: { PrintLn("andere"); break; }
    }
    return 0;
}

Kein Fall-through

Jeder Zweig muss mit break oder return enden — auch der letzte und auch default. Fehlt es, meldet der Compiler:

sema error: switch case may fall through; add 'break' or 'return'

Das kehrt das Verhalten von C um: Dort fällt ein Zweig ohne break in den nächsten, und das vergessene break ist die häufigste Fehlerquelle des Konstrukts. In Lyx ist es umgekehrt ein Übersetzungsfehler.

Konstantenausdrücke als Label

Ein case-Label darf jeder zur Übersetzungszeit berechenbare Ausdruck sein — ein Literal, eine con-Konstante oder eine Rechnung daraus:

con GRENZE: int64 := 5;

switch (wert) {
    case GRENZE:  { PrintLn("an der Grenze"); break; }
    case 2 * 3:   { PrintLn("sechs"); break; }
    default:      { break; }
}

switch oder match?

Kriterium switch match
Als Ausdruck verwendbar Nein Ja — var r := match (x) { … }
Auffangfall default: case _ ⇒
Bereiche Nein Ja — case 0..500 =>
Guards Nein Ja — case 5 if x > 3 ⇒
Zweig-Abschluss break/return Pflicht nicht nötig

Pattern Matching — match im Detail


7. MC/DC und DO-178C Relevanz

Für Luftfahrt-Zertifizierung (DAL A/B) reicht Branch-Coverage nicht aus — gefordert ist Modified Condition/Decision Coverage (MC/DC).

// Drei Bedingungen — für MC/DC braucht man 4 Testfälle (nicht 8)
fn IsLandingAllowed(gear_down: bool, speed_ok: bool, runway_clear: bool): bool {
    return gear_down & speed_ok & runway_clear;
}

Der Compiler instrumentiert alle Bedingungen für MC/DC-Analyse:

// Build mit MC/DC-Instrumentierung:
// lyxc --mcdc-report src/flight.lyx -o /dev/null
// Achtung: zaehlt a && b als EINE Bedingung (#1524) — die Mindestzahl
// der Testfaelle ist von Hand herzuleiten
//
// Nach dem Testlauf Coverage-Report erzeugen:
// Eine HTML-Aufbereitung gibt es nicht (#1524)

Jede Teilbedingung in einem zusammengesetzten Ausdruck muss unabhängig das Gesamtergebnis beeinflussen können. Der Report zeigt welche Test-Vektoren dies nachweisen.

→ Vollständige Dokumentation: DO-178C Compliance


8. Kein Präprozessor — Compile-Zeit-Bedingungen in Lyx

Lyx hat keinen Präprozessor und keine #ifdef/#if-Direktiven. Das ist eine bewusste Designentscheidung: Präprozessor-Konditionierung macht statische Analyse, MC/DC-Coverage und WCET (@wcet)-Berechnung schwieriger, weil der Compiler immer nur eine Variante des Codes sieht.

Stattdessen gibt es drei saubere Alternativen:

Alternative 1: con-Konstanten (Compile-Zeit-Auswertung)

Konstante Ausdrücke mit con werden zur Compile-Zeit ausgewertet. Der Compiler eliminiert dead branches automatisch:

con DEBUG_BUILD: bool := false;

fn LogMessage(msg: pchar): void {
    if (DEBUG_BUILD) {
        Print(msg);   // Compiler entfernt diesen Block im Release-Build vollständig
    }
}

Der Unterschied zu #ifdef DEBUG: Der Code ist immer syntaktisch und typkorrekt geprüft — auch der „tote“ Zweig wird geparst und typgeprüft. Tippfehler in Debug-Code werden nicht erst beim Debug-Build entdeckt.

Alternative 2: Separate Unit-Dateien pro Plattform

Für plattformspezifische Implementierungen werden separate Units angelegt und per Import ausgewählt:

src/
  platform/
    timer_linux.lyx     // unit platform.timer;  — Linux-Implementierung
    timer_esp32.lyx     // unit platform.timer;  — ESP32-Implementierung
  main.lyx

# Linux-Build
lyxc main.lyx -I src/platform/linux -o app

# ESP32-Build
lyxc main.lyx -I src/platform/esp32 --target=esp32 -o app.elf

Der import platform.timer; in main.lyx bleibt identisch — der –include-Pfad entscheidet, welche Implementierung genommen wird.

Alternative 3: @target-spezifische Attribute

Architektur-spezifisches Verhalten wird über @section, @energy und @extern gesteuert — nicht über Textsubstitution:

// Immer korrekt — der Compiler wählt die passende Codegen-Strategie
@energy(1)
fn SleepMs(ms: int64): void {
    // Auf ARM: WFI-Instruktion; auf x86: PAUSE + HLT
    // Der Compiler kennt das --target und erzeugt plattformgerechten Code
}

C-Präprozessor-Muster Lyx-Äquivalent
#ifdef DEBUG con DEBUG: bool := false; + normale if-Bedingung
#ifdef linux Separate -I-Pfade pro Plattform
#define MAX_SIZE 1024 con MAX_SIZE: int64 := 1024;
#ifdef ARM / #ifdef X86 –target=arm64 / –target=linux + plattformspezifische Units
#pragma once Nicht nötig — jede .lyx-Datei ist eine Unit mit eindeutigem Namen

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).