Skip to main content

Week 5: Failure is a value, not an exception

The goto field made precise — every statement succeeds or fails, and that signal is SNOBOL4's entire branching mechanism

22 March 2027

Outline

The goto field, made precise

Week 2 stated the headline fact and left it there: SNOBOL4 has no if, while, or for, and the goto field is the only branching primitive in the language. This week is about taking that fact seriously enough to build an interpreter around it, which means being exact about what the goto field actually is.

Every statement can end with a goto field: :S(label), :F(label), both together in either order (:S(label)F(label)), or an unconditional :(label). Which branch fires is decided by whether the statement as a whole succeeded or failed — not by evaluating some separate boolean expression, because there isn’t one. A labeled statement with a success-goto back to itself and a failure-goto out is SNOBOL4’s entire loop construct:

LOOP    OUTPUT = INPUT      :S(LOOP)

reads and echoes records until INPUT fails at end of file, then falls through to the next statement. There is no condition being tested here in the sense a while loop tests one — there is a statement that either works or doesn’t, and a label to go to depending on which.

Everything that can fail

The reason this is a genuine control-flow primitive, and not a narrow feature bolted onto pattern matching, is that failure is a property of any statement, not a special result type that only patterns produce. The manual’s own list of what can fail includes a pattern that doesn’t match (which this course reaches properly in Movement III), an INPUT read that hits end of file, an array reference outside its declared bounds, a predicate function, and a user-defined function that transfers control to the system label FRETURN rather than RETURN. Once a compiler implementer accepts that failure is a uniform signal produced by the evaluation of a statement — assignment included — rather than a pattern-specific outcome, a lot of what reads as a pile of unrelated SNOBOL4 features (array-bounds-as-loop-exit, FRETURN as “the same mechanism as a failed pattern match”) turns out to be one idea, applied consistently.

Predicates: success without a value worth keeping

A predicate is a function whose entire purpose is the fact of its success or failure, not any value it returns. LT(N1,N2) “returns true” only in the loose sense that on success it returns the null string; that return value is never inspected for truthiness, because the only thing a goto field can dispatch on is whether the call succeeded at all. This is the single most common misconception students bring into this week: a habit, built from years of if (lessThan(a, b)), of assuming a predicate hands back a boolean you could store in a variable and test later. SNOBOL4’s predicates don’t hand back anything meaningful to store — the fact of success or failure is the entire signal, observable only through a goto field or a further pattern-match-style use. Ordinary functions (SIZE, DUPL, REPLACE, …) are distinguished from predicates (LT, LE, EQ, NE, GE, GT, IDENT, DIFFER, …) by exactly this: whether the return value or the success/failure is the point.

Computed labels: the same indirect-reference operator, again

Week 2 introduced the unary $ operator for turning a string value into “the variable named by that string.” The same operator works inside a goto field, for a computed jump target: :($('PHASE' N)) builds a label by concatenating PHASE with the current value of N, then jumps there. This is worth calling out on its own, not as a footnote, because it means the goto field and ordinary variable reference are not two features that happen to share a symbol — they are the same primitive, dereferencing a computed name, used in two syntactic positions. A compiler that implements $ once, generally, gets computed gotos for free rather than as a second feature.

What Movement II is building

This week’s studio takes the expression evaluator from Movement I and wraps it in a statement executor: a program becomes a sequence of labeled statements, executed by a program counter rather than evaluated once and discarded, with the goto field driving where that counter goes next based on success or failure. Next week adds DEFINEd functions on top of this same execution model — a function call is, from the caller’s point of view, just another statement that can succeed or fail, which is exactly why it slots into the goto-field machinery this week builds rather than needing a separate calling convention bolted on afterward.

Further reading

Teaching team