Skip to content

Conditionals — If / Else / Switch / Case

The new parser adds proper structured conditionals, replacing the low-level JumpIf / JumpIfNot / Mark / JumpTo cluster for the common cases.

If / Else

The basic shape:

If (<condition>) {
  ... rules to run when condition is true ...
} Else {
  ... rules to run when condition is false ...
}

The condition is an infix expression — comparisons return booleans, which the If consumes. Both branches are blocks of rules; either branch may be omitted (Else is optional). Variables modified inside a branch persist into the rest of the chain.

Worked example — fallback when a primary lookup is null

Source intnum.csv has four integer columns. The primary lookup sometimes returns NULL; in that case, fall back to a different source field through a different Usagi map.

logic: :int1 => UsagiMap("A.csv", NULL)[] => :var1
logic: If (:var1 = NULL) {
logic:   :int4 => UsagiMap("D.csv", NULL)[] => :var1
logic: }

Read top-down:

  1. Run UsagiMap("A.csv") against :int1, defaulting to NULL on a miss. Store the multi-result in :var1.
  2. If :var1 is NULL, replace it with the result of running UsagiMap("D.csv") against :int4.

Note = is the comparison operator (not ==).

When to use If vs ||

The single-rule || is shorter when both branches are one rule:

:int1 => UsagiMap("A.csv") || UsagiMap("D.csv")

If wins when:

  • A branch has multiple rules to execute as a unit.
  • The branches operate on different inputs (the || form requires the same input for both alternatives).
  • A condition is expressed by comparison rather than by "did the rule succeed".

For multi-rule branches that share an input, the block fallback form { ... }(:var) || { ... }(:var) is also worth considering — it keeps the "first success wins" semantics of || while letting each branch be a block.

Switch / Case

A clean cascade for many small lookups against a single value:

logic: Switch (:intnum2) {
logic:   Case 1: SetValue(38003563)
logic:   Case 2: SetValue(38003564)
logic:   Case 3: SetValue(38003565)
logic:   Default: SetValue(0)
logic: }

Equivalent to a chain of If/ElseIfs, but reads more naturally when the cases are all literal values against the same expression.

Worked example — concept-id by enum

Source intnum1 carries a small set of enum-style integers. The target CDM field gender_concept_id is a real OMOP concept id, so each enum value maps to a different concept:

logic: Switch (:intnum1) {
logic:   Case 999: SetValue(9999)
logic:   Case 100: SetValue(8507)
logic:   Case 200: SetValue(8532)
logic:   Default: SetValue(0)
logic: }

For string keys this is essentially what Map([...], default) does in one line. Switch is the better choice when the right-hand side of each branch is a block of rules rather than just a value.

How conditionals desugar

Under the hood, If / Else and Switch / Case compile down to the JumpIf, JumpIfNot, JumpTo, and Mark rules. You can write those directly if you need control flow that the structured forms do not express, but the structured forms are strongly preferred — they're easier to read and harder to get wrong.

See also

Source tests

tests/test062/If for null-fallback after a multi-result UsagiMap. tests/test067/Switch / Case for enum-to-concept-id mapping.