CASE / OF / ELSE / END_CASE

Definition

Multi-way dispatch on an integer-valued expression (the scrutinee). The body lists one or more case-labels followed by a colon and a statement-list; the scrutinee value is matched against each label and the first matching branch runs. An optional ELSE branch is the catch-all.

Case labels may be:

  • A single value (5:),
  • A list of values (1, 3, 5:),
  • A range of values (1..10:),
  • Any combination of the above (1, 3, 5..10:).

Case labels MUST be unique across the whole CASE block; the scrutinee MUST be of an integer type (SINT, INT, DINT, LINT and their unsigned variants); labels MUST be compile- time constant expressions of an integer type compatible with the scrutinee.

Syntax

CASE <int_expr> OF
    <label_list_1>:
        <statement_list_1>
    [<label_list_n>:
        <statement_list_n>]*
[ELSE
    <statement_list_else>]
END_CASE;

Example

(* Bit-mask lookup by step index. CASE reads cleaner than a
   long IF/ELSIF cascade for purely numeric dispatch. Note the
   plain decimal literals on the right-hand side — see the
   matiec-quirk warning above. *)
CASE iStep OF
    0: bMask := INT_TO_BYTE(1);
    1: bMask := INT_TO_BYTE(2);
    2: bMask := INT_TO_BYTE(4);
    3: bMask := INT_TO_BYTE(8);
    4: bMask := INT_TO_BYTE(16);
    5: bMask := INT_TO_BYTE(32);
ELSE
    bMask := INT_TO_BYTE(0);
END_CASE;

Semantics

The scrutinee is evaluated once. The branches are scanned top-to-bottom; the first label-list that contains the scrutinee value wins. Label-list overlap is forbidden by the standard, but matiec only catches obvious overlaps (the same literal in two label lists). Numeric ranges are inclusive on both ends (1..3 matches 1, 2, 3).

If no label matches and no ELSE branch exists, the CASE block is a no-op.

IEC reference

IEC 61131-3 third edition (2013), clause 6.6.3.2 — “CASE statement”. Keywords CASE, OF, ELSE, END_CASE are reserved.

matiec conformance

Two known quirks worth flagging up front:

Hex literal in case labels (see llm_signals above). matiec’s front-end lexer mis-tokenises 16#01 when it appears as a case label. The diagnostic is the unhelpful Unexpected token: '01'. Workaround: use decimal labels (1, 2, 4, …) — bare integer literals widen to the scrutinee’s type implicitly.

Bit-shift via SHL in case bodies is unrelated to CASE itself but co-occurs frequently because both come up in bit-mask code. matiec emits broken C for SHL(BYTE#1, INT_TO_UINT(x)) (it generates a data__->BYTE struct-member access for the typed-literal). Avoid SHL in matiec; do the dispatch with CASE (this page) or IF/ELSIF (../if-statement/) and a literal table.

ForgeIEC notes

The Ackersteuerung example project (2026-05-14 cleanup, see project_ackersteuerung_save_load_bug memory) hits both quirks in one place: FB_MANUAL_TIME originally used SHL(BYTE#1, iMtStep) to compute a per-step bit-mask. After SHL was rejected by matiec the natural rewrite to CASE OF with hex labels then hit the Unexpected token: '01' quirk on 16#01. The working form (currently in production):

IF iMtStep = 0 THEN
    bMtMask := INT_TO_BYTE(1);
ELSIF iMtStep = 1 THEN
    bMtMask := INT_TO_BYTE(2);
ELSIF iMtStep = 2 THEN
    bMtMask := INT_TO_BYTE(4);
ELSIF iMtStep = 3 THEN
    bMtMask := INT_TO_BYTE(8);
ELSIF iMtStep = 4 THEN
    bMtMask := INT_TO_BYTE(16);
ELSIF iMtStep = 5 THEN
    bMtMask := INT_TO_BYTE(32);
ELSE
    bMtMask := INT_TO_BYTE(0);
END_IF;

The same logic in pure CASE works once the labels are decimal, but the IF/ELSIF cascade is what is currently deployed and in scan-cycle.