Control Flow in SNOBOL
Learn how SNOBOL drives program flow with success/failure, conditional gotos, comparison predicates, and pattern-matching loops
Most languages give you if, else, for, and while as dedicated keywords. SNOBOL has none of them. Instead, control flow grows out of a single idea that runs through the entire language: every statement either succeeds or fails, and that result decides where execution goes next.
Coming from the SNOBOL4 lineage at Bell Labs, this is a pattern-directed, imperative model. A statement runs, the interpreter notes whether it succeeded or failed, and an optional goto field at the end of the line — written :S(LABEL) for success, :F(LABEL) for failure, or :(LABEL) unconditionally — sends execution to a labeled statement. There are no block structures and no indentation rules to obey; flow is a graph of labels connected by gotos.
The things that “fail” are richer than a boolean test. A numeric comparison like GT(N, 0) fails when it isn’t true. A pattern match fails when the pattern isn’t found. An input read fails at end of file. Because all of these share the same success/failure signal, the same :S/:F machinery handles conditionals, loops, multi-way branches, and string scanning alike.
In this tutorial you’ll build conditionals from comparison predicates, write loops with nothing but a label and a goto, drive a loop with the success or failure of a pattern match, and use SNOBOL’s idiom for conditional (“ternary”) assignment.
Conditionals: Predicates and Gotos
SNOBOL has no if keyword. Instead it offers predicate functions that succeed or fail. For numbers there are EQ, NE, LT, LE, GT, and GE. For strings — and this surprises newcomers — you do not use = (that’s assignment). You use IDENT (identical) and DIFFER (different). A predicate returns the null string on success and fails otherwise, so you wire it straight into a goto field.
Create a file named control_flow_if.sno:
| |
GT(N, 0) succeeds for N = 7, so :S(POS) jumps to the POS label. After printing, :(STR) jumps unconditionally past the other branches — SNOBOL has no automatic “end of if”, so you must route around the branches you didn’t take. The IDENT test then compares two strings the SNOBOL way.
Loops: A Label and a Goto
A loop is just a statement that jumps back to its own label. There is no while or for — you repeat by gotoing backward, and you exit when a predicate fails. This file shows two classic shapes: a countdown that loops while a condition holds, and a counter that accumulates a running total.
Create a file named control_flow_loops.sno:
| |
In the countdown, GT(N, 0) keeps succeeding — and looping back to DOWN — until N reaches 0, at which point it fails, execution falls through, and “Liftoff!” prints. The summation loop runs UP while LE(I, 9) holds, so it processes I from 1 through 10 and stops once I becomes 10.
Multi-Way Branching: FizzBuzz
When you need several mutually exclusive cases — the equivalent of switch/case — you chain predicate tests, each with a success goto to its own labeled action. Combined with a backward loop, this gives a compact FizzBuzz. The REMDR(a, b) built-in returns the remainder of a divided by b.
Create a file named control_flow_fizzbuzz.sno:
| |
Each iteration tests the divisibility cases in order, jumping to the first one that succeeds. The :(LOOP) after every action sends control back to the top, and GT(N, 15) ends the program by jumping to END once the count is exhausted. Note that the most specific case (REMDR(N, 15)) is tested first — order matters because the first success wins.
Pattern Matching as Control Flow
This is where SNOBOL diverges most sharply from other languages: a pattern match is a control-flow test. The match succeeds or fails just like a comparison, so you loop by repeatedly matching and use :F to detect when there’s nothing left to match. Here we peel words off a sentence one at a time. BREAK(" ") matches everything up to the next space, the . W operator captures that matched text into W, and assigning null (= with nothing after it) deletes the matched portion from the subject.
Create a file named control_flow_patterns.sno:
| |
Each pass through NEXT finds a word followed by a space, prints the captured W, and shaves it off TEXT. When only "fox" remains there’s no trailing space, so BREAK(" ") fails, the match fails, and :F(LAST) jumps out to print the final leftover word. The loop’s termination is the failure of a pattern match — pure SNOBOL.
Conditional Expressions: SNOBOL’s “Ternary”
SNOBOL has no ? : ternary operator, but it doesn’t need one. Because a failed statement performs no assignment, you can guard an assignment with a predicate: if the predicate fails, the variable keeps its previous value. Concatenating a predicate’s null result with a string yields a conditional value.
Create a file named control_flow_ternary.sno:
| |
LABEL starts as "zero". The line LABEL = GT(N, 0) "positive" fails because GT(-4, 0) fails, so the assignment never happens and LABEL is untouched. The next line succeeds — LT(-4, 0) returns null, null "negative" is just "negative" — so LABEL becomes "negative". This guarded-assignment pattern is how SNOBOL programmers express “set this value only when a condition holds.”
Running with Docker
| |
Expected Output
control_flow_if.sno:
7 is positive
Name matches
control_flow_loops.sno:
5
4
3
2
1
Liftoff!
Sum 1..10 = 55
control_flow_fizzbuzz.sno:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
control_flow_patterns.sno:
the
quick
brown
fox
control_flow_ternary.sno:
-4 is negative
Key Concepts
- Success and failure are the engine — every statement reports one or the other, and that single signal powers conditionals, loops, branching, and string scanning alike.
- Gotos are the syntax of control flow —
:S(LABEL)on success,:F(LABEL)on failure, and:(LABEL)unconditionally; there are no blocks, so you route explicitly around branches you don’t take. - Predicates replace
if—EQ,NE,LT,LE,GT,GEtest numbers and succeed or fail. Compare strings withIDENTandDIFFER, never with=(which is assignment). - Loops are backward gotos — repeat by jumping to a label and exit when a predicate fails; there is no dedicated
fororwhile. - Multi-way branching is a chain of tests — list predicate-plus-
:Slines in priority order; the first one to succeed wins, so order the most specific case first. - Pattern matches are control-flow tests — a match that fails (
:F) is the idiomatic way to end a string-processing loop, the language’s signature technique. - A failed statement assigns nothing — guarding an assignment with a predicate gives you conditional (“ternary”) assignment without any special operator.
- Labels live in column 1; statements are indented — every labeled action and every goto target hangs off a name in the first column, while ordinary statements start with whitespace.
Running Today
All examples can be run using Docker:
docker pull esolang/snobol:latest
Comments
Loading comments...
Leave a Comment