Chapters

Control flow in Catena

Most of a Catena sequence runs straight down the page: one line, one instruction, top to bottom. Control flow is what lets a sequence do something other than that — to choose between two courses of action, or to repeat a piece of work several times. These are the constructs that turn a fixed list of steps into a program that reacts to its own values.

This chapter is concept-first. Each section explains the idea in plain terms, shows the exact Catena keywords, then gives a worked sequence you can read straight through. If you have written programs before, the shapes will be familiar; the keywords are deliberately chosen to read like English, so REPEAT 5 TIMES means what it says. If sequences are new to you, start with Sequences and functions first, then come back here.

Two conventions appear in every example below. The arrow -> stores a value into a name (total + price -> total means "add price to total and keep the result in total"). Anything after a # on a line is a comment for you, ignored when the sequence runs.


Making a decision: IF

An IF looks at a condition — a question with a yes/no answer — and runs a block of lines only when the answer is yes. It is evaluated once, at the point where it sits in the sequence. Every IF is closed with END, so a block always has a clear beginning and end; Catena never relies on indentation to tell where a block stops.

IF discriminant < 0
    RETURN "no real roots"
END

When you need a fallback for the "no" case, add an OTHERWISE branch (its alias is ELSE). Exactly one of the two blocks runs:

IF amount > 5000
    amount × 1.05 -> amount        # large orders take a surcharge
OTHERWISE
    amount -> amount               # small orders unchanged
END

To test several conditions in turn, chain them with OTHERWISE IF (alias ELSE IF). Catena checks each condition from the top and runs the block for the first one that is true; the trailing OTHERWISE catches everything that matched none of them:

IF score ≥ 90
    "A" -> grade
OTHERWISE IF score ≥ 75
    "B" -> grade
OTHERWISE IF score ≥ 60
    "C" -> grade
OTHERWISE
    "F" -> grade
END

The UNLESS shorthand. People often state a rule as a general case with one exception — "charge full price, unless the customer is a member." UNLESS writes that directly. It is a first-class negated conditional: UNLESS condition runs its block when the condition is false.

UNLESS member
    price × 1.20 -> price          # non-members pay the markup
END

UNLESS member reads more naturally than IF NOT member and means exactly that. Use IF when you think of the normal case as the condition being true, and UNLESS when the block is the exception to a general rule.

!

WHEN is not a synonym for IF. WHEN introduces a reactive rule that re-runs whenever its inputs change, which is a different idea entirely — see Reactivity. An IF decides once, here; a WHEN keeps watching.


Repeating work: the loop forms

A loop runs the same block of lines more than once. Catena has five ways to write one, and the right choice depends on what you already know when you start: a fixed count, a range of numbers, the contents of a bag, or a condition to watch. Every loop, whatever its form, opens with REPEAT or FOR EACH and closes with END.

You want to... Use You know in advance
Do something a set number of times REPEAT n TIMES how many times
Count through a range of numbers REPEAT WITH i FROM a TO b the first and last number
Handle every item in a collection FOR EACH item IN bag the collection, not its length
Keep going while something stays true REPEAT WHILE condition only a stop condition
Keep going until something becomes true REPEAT UNTIL condition only a stop condition

Counted repetition — REPEAT n TIMES. The simplest loop. Run the block a fixed number of times; you do not need a counter variable.

REPEAT 3 TIMES
    PROMPT "Reading?" -> r          # ask for three readings in a row
    total + r -> total
END

Ranged repetition — REPEAT WITH i FROM a TO b. When you do want the count itself — to number the steps, or to compute with it — bind a name to it. The name takes each whole value from the start up to and including the end.

REPEAT WITH i FROM 1 TO 10
    total + i -> total             # add 1, then 2, ... up to 10
END
# total is now 55

Iterating a bag — FOR EACH … IN …. A bag is Catena's collection type (see Bags). FOR EACH walks through its items one at a time, binding each to a name, without you tracking a position or a length:

FOR EACH price IN prices
    total + price -> total
END

This reads as plain English and is the natural way to total, scan, or transform a collection. Where a sequence only needs a sum, an average, or a count of a bag, the built-in aggregate functions are shorter still — reach for a FOR EACH when you need to do real per-item work.

Conditional repetition — REPEAT WHILE and REPEAT UNTIL. Sometimes you cannot say up front how many passes you need; you only know the condition that should stop the loop. These two forms watch a condition instead of a count. REPEAT WHILE keeps going while its condition stays true; REPEAT UNTIL keeps going until its condition becomes true — the same idea stated from the two opposite ends.

REPEAT WHILE NOT converged
    refine -> estimate             # improve the estimate each pass
END

REPEAT UNTIL balance ≤ 0
    balance − payment -> balance   # pay down until nothing is left
END

These are the loops you use for convergence work — successive approximation, paying down a balance, iterating toward a root. Pick whichever phrasing makes the stopping rule read most clearly.


Every loop is bounded: MAXLOOP

A conditional loop only stops when its condition finally flips. If the condition can never be met — a subtraction that never reaches zero, an estimate that never settles — a naive calculator would spin forever. Catena does not let that happen. Every loop is capped by a maximum number of iterations, the MAXLOOP engine setting. When a loop hits the cap, the sequence stops with a clear, friendly error telling you the limit was reached — never a frozen, unresponsive app.

The cap has a sensible engine default, and you rarely need to touch it. When a genuinely long computation needs more room (or you want a tighter guard while testing), set it inside the sequence with an engine setting:

ESET MAXLOOP 100000                # raise the per-loop iteration cap

An engine setting travels with the sequence and applies to it and to everything it calls, so the same limits hold wherever the sequence runs. The setting is covered in full in Engine settings. The point to remember here is the guarantee: a Catena sequence can never hang in a loop.


Re-running a step: CHECKPOINT and JUMP TO

Occasionally you need to send control back to an earlier point — most often to re-ask for input that failed a check. Catena provides a named marker and a jump for exactly this. CHECKPOINT name (alias CP) marks a spot; JUMP TO name (alias GOTO) sends control back to it. Checkpoints are always named, never numbered.

CHECKPOINT retry
PROMPT "Net price?" -> price
UNLESS price > 0
    JUMP TO retry                  # not positive: ask again
END

This is the input-validation loop: mark the prompt, read a value, and jump back to the mark unless the value is acceptable.

The limits — and why they exist. Catena's jump is deliberately scoped, closer to a modern language's goto than the free-for-all of early BASIC. Two rules keep it from turning into tangled spaghetti:

  • A jump stays within the same sequence. You cannot jump into another sequence.
  • A jump cannot land in the middle of a block — you cannot jump into the inside of an IF, an UNLESS, or a loop from outside it.

Within those limits, JUMP TO is the clearest tool for a re-prompt and little else. For ordinary repetition, the loop forms above read better and are what you should reach for first.


Leaving a sequence early: RETURN

To stop a sequence before its last line — because a decision has already settled the answer — use RETURN. On its own it ends the sequence; followed by a value, it hands that value back as the result. You saw it in the first IF example: RETURN "no real roots" abandons the rest of the work the moment the discriminant proves there is nothing more to compute. Placed inside a loop, a RETURN ends the whole sequence at once. RETURN is described alongside the rest of sequence authoring in Sequences and functions.


Worked examples

Example 1 — a decision (IF / OTHERWISE IF). Convert a numeric mark into a letter grade. The chain tests the boundaries from the top down and takes the first that matches:

PROMPT "Mark out of 100?" -> score
IF score ≥ 90
    "A" -> grade
OTHERWISE IF score ≥ 75
    "B" -> grade
OTHERWISE IF score ≥ 60
    "C" -> grade
OTHERWISE
    "F" -> grade
END
RETURN grade

A mark of 82 fails ≥ 90, passes ≥ 75, and the sequence returns "B" — the later branches are never even tested.

Example 2 — a ranged loop (REPEAT WITH). Add the whole numbers from 1 to 10 by hand, to show the counter in use:

0 -> total
REPEAT WITH i FROM 1 TO 10
    total + i -> total
END
RETURN total                        # 55

The name i takes the values 1, 2, 3, … 10 in turn; each pass adds the current i to the running total, which ends at 55.

Example 3 — a bag loop (FOR EACH). Total a shopping basket held in a bag called prices, applying a discount to each item over 100 as you go:

0 -> total
FOR EACH price IN prices
    IF price > 100
        price × 0.90 -> price       # 10% off the pricier items
    END
    total + price -> total
END
RETURN total

Here a loop and a decision work together: FOR EACH visits every price, and the nested IF adjusts only the ones above the threshold before they are added. This is the everyday shape of control flow — repeat over a collection, decide per item, accumulate a result.


See also

  • Sequences and functions — writing a sequence, parameters, and RETURN.
  • Bags — the collection type that FOR EACH walks over.
  • ReactivityWHEN rules, which re-run on change and are not conditionals.
  • Engine settingsMAXLOOP and the other settings that travel with a sequence.