Functions in SNOBOL
Learn how to define, call, and return from functions in SNOBOL, including recursion, local scope, built-in functions, and success/failure returns
Functions in SNOBOL look unlike functions in almost any modern language. There is no def, function, or return-type keyword. Instead, a function is created at runtime by calling the built-in DEFINE, which registers a name, its parameters, and its entry point. The body is just ordinary SNOBOL statements sitting in your program, reached by a goto when the function is called.
What ties it all together is SNOBOL’s defining trait: every statement either succeeds or fails, and functions participate in that flow directly. A function returns its value by assigning to a variable that shares its own name and then transferring to the special label RETURN. If something goes wrong, it can transfer to FRETURN instead — and the call itself fails, letting the caller branch on :S(...)F(...) exactly as it would for a pattern match.
In this tutorial you’ll define functions with parameters, return values, and local variables; write a recursive factorial; explore SNOBOL’s many built-in functions; and use failure returns to signal “no result.” Because SNOBOL is dynamically and weakly typed, a single function can return a string, an integer, or a pattern depending on what it computes.
Defining and Calling a Function
A function is declared with DEFINE('NAME(PARAM1,PARAM2)'). After the DEFINE statement you immediately goto a label past the function body, so the body isn’t executed while the program is “falling through.” The body lives at a label matching the function name, and it returns by assigning to the variable named after the function and jumping to RETURN.
Create a file named functions.sno:
| |
The :(GREET_END) on the DEFINE line skips over the body during normal flow. When GREET("World") is called, control jumps to the GREET label, builds the greeting by concatenation, stores it in the variable GREET, and :(RETURN) hands that value back to the caller.
Recursion
SNOBOL functions can call themselves, and recursion is the natural way to express repeated computation that returns a value. Here is the classic factorial. The predicate EQ(N,0) succeeds (returning the null string) only when N equals 0, which gives us the base case.
Create a file named recursion.sno:
| |
On the line FACT = EQ(N,0) 1, if N is 0 the EQ predicate succeeds, its null result concatenates with 1 to give "1", and :S(RETURN) returns immediately. If EQ fails, the whole statement fails, the assignment never happens, and control falls through to the recursive multiplication.
Local Variables and Scope
By default, names in SNOBOL are global. To get a local variable, list it after the parameter list in DEFINE: 'DOUBLE(X)TEMP' makes X a parameter and TEMP a local. SNOBOL saves the previous value of each local on entry and restores it when the function returns, so the function can’t clobber a global of the same name.
Create a file named scope.sno:
| |
Even though DOUBLE assigns to TEMP internally, the global TEMP is untouched because the local declaration shadows it for the duration of the call.
Built-in Functions
SNOBOL4 ships with a rich set of built-in functions for string work. You call them exactly like your own functions. A few of the most useful:
SIZE(s)— number of characters insREVERSE(s)—sreversedDUPL(s, n)—srepeatedntimesREPLACE(s, set1, set2)— translate each character ofsfound inset1to the matching character inset2TRIM(s)— remove trailing blanks
Create a file named builtins.sno:
| |
REPLACE('CAT', 'ABC', 'XYZ') maps A→X, B→Y, C→Z, leaving any character not in 'ABC' unchanged — so C becomes Z, A becomes X, and T stays T.
Returning Success or Failure
This is where SNOBOL functions shine. A function returns through RETURN on success or FRETURN on failure. When a function takes the FRETURN path, the call expression itself fails, so the caller can branch with :S(...)F(...). Here FIRSTVOWEL returns the first vowel of a word, or fails if there is none.
Create a file named failure.sno:
| |
The pattern WORD ANY('aeiouAEIOU') . FIRSTVOWEL matches the first vowel and, via the conditional assignment operator ., stores it in FIRSTVOWEL. If no character matches, the match fails and :F(FRETURN) makes the whole call fail — so FIRSTVOWEL("sky") causes the assignment to fail and the caller jumps to NONE1.
Running with Docker
| |
Expected Output
functions.sno:
Hello, World!
Hello, SNOBOL!
recursion.sno:
0! = 1
5! = 120
10! = 3628800
scope.sno:
42
global value
builtins.sno:
SIZE('SNOBOL'): 6
REVERSE('SNOBOL'): LOBONS
DUPL('ab', 3): ababab
REPLACE('CAT', 'ABC', 'XYZ'): ZXT
TRIM('hi '): [hi]
failure.sno:
sky has no vowel
hello -> e
Key Concepts
- Functions are defined at runtime —
DEFINE('NAME(PARAMS)LOCALS')registers a function; there is no compile-timedefkeyword. - The body is reached by goto — put
:(label_END)on theDEFINEline to skip the body during normal flow; the entry point is the label matching the function name. - Return value is the function-named variable — assign to
NAMEinside the body and transfer toRETURN; the value handed back is whateverNAMEholds. - Functions can succeed or fail —
RETURNsucceeds,FRETURNfails, and the call fails too, so callers branch with:S(...)F(...)just like a pattern match. - Locals are declared after the parameter list — names listed after the parens (e.g.
'DOUBLE(X)TEMP') are saved and restored across the call; everything else is global. - Recursion is natural — a function may call itself; predicates like
EQ,LT, andGTgive you base cases by succeeding or failing. - Dynamic, weak typing — a single function can return a string, an integer, or a pattern, and SNOBOL converts between numbers and their string forms automatically.
- No default parameters — SNOBOL has no default-argument syntax; omitted arguments simply arrive as the null string, which you can test for explicitly. Indirect calls are possible via
APPLY(name, args...)and the$indirection operator.
Running Today
All examples can be run using Docker:
docker pull esolang/snobol:latest
Comments
Loading comments...
Leave a Comment