Beginner

Variables and Types in SNOBOL

Learn how SNOBOL handles dynamically typed variables, automatic conversions, and pattern values as first-class data

SNOBOL4 has one of the most permissive type systems of any classic language. There are no type declarations, no var or let keywords, and no fixed types attached to names — a variable simply becomes whatever value you assign to it, including patterns, tables, and arrays. This freedom is essential to SNOBOL’s pattern-directed paradigm: because patterns are themselves first-class values that you build at runtime, the language cannot meaningfully restrict what a variable holds.

SNOBOL is dynamically and weakly typed. Strings, integers, and real numbers convert automatically when an operator or built-in expects a particular form. Add 1 to "100" and SNOBOL silently produces the integer 101; concatenate a number into a string and the number quietly becomes its string representation. The type machinery is invisible most of the time, but DATATYPE() lets you peek at what a value actually is.

This tutorial covers how variable assignment works in SNOBOL, the built-in data types you will meet, the rules for automatic and explicit conversion, and how unset variables and patterns fit into the picture.

Variable Assignment and Built-in Types

A SNOBOL variable comes into existence the moment you assign to it. The same name can hold an integer on one line, a string on the next, and a pattern after that. The built-in DATATYPE() function returns the current type as a string such as INTEGER, STRING, REAL, PATTERN, ARRAY, or TABLE.

Create a file named variables.sno:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
*       Basic variables of different types
        COUNT = 42
        GREETING = "Hello"
        PI = 3.14159
        EMPTY =

*       OUTPUT prints each assigned value as a line
        OUTPUT = "COUNT    = " COUNT
        OUTPUT = "GREETING = " GREETING
        OUTPUT = "PI       = " PI
        OUTPUT = "EMPTY    = '" EMPTY "'"

*       DATATYPE reveals the runtime type
        OUTPUT = "type of COUNT    = " DATATYPE(COUNT)
        OUTPUT = "type of GREETING = " DATATYPE(GREETING)
        OUTPUT = "type of PI       = " DATATYPE(PI)
        OUTPUT = "type of EMPTY    = " DATATYPE(EMPTY)
END

A few things to notice:

  • EMPTY = with nothing on the right assigns the null string, which is also the value every unreferenced variable starts with. SNOBOL has no separate null or nil — the empty string plays both roles.
  • Adjacent values such as "COUNT = " COUNT are concatenated. There is no + for strings; juxtaposition is the operator.
  • Variable names are conventionally uppercase. SNOBOL is case-sensitive, and lowercase names are valid but unusual.

Automatic Type Conversion

Because SNOBOL is weakly typed, operators drive conversion. Arithmetic operators force their operands toward numbers; string contexts force them toward strings. There is no error for “wrong type” as long as the value can be reasonably interpreted.

Create a file named conversions.sno:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
*       String that looks numeric -> integer arithmetic
        S = "100"
        N = S + 25
        OUTPUT = "'100' + 25 = " N
        OUTPUT = "type of N  = " DATATYPE(N)

*       Mixing INTEGER and REAL promotes to REAL
        MIX = 7 + 0.5
        OUTPUT = "7 + 0.5    = " MIX
        OUTPUT = "type of MIX = " DATATYPE(MIX)

*       Numbers concatenate cleanly into strings
        LABEL = "answer=" 42
        OUTPUT = "concat result = " LABEL
        OUTPUT = "type of LABEL = " DATATYPE(LABEL)

*       Explicit conversion with CONVERT()
        TEXT = CONVERT(3.14, "STRING")
        BACK = CONVERT("256", "INTEGER")
        OUTPUT = "CONVERT(3.14,'STRING') = " TEXT " (" DATATYPE(TEXT) ")"
        OUTPUT = "CONVERT('256','INTEGER') = " BACK " (" DATATYPE(BACK) ")"
END

CONVERT(value, target) is the explicit form. It fails (in SNOBOL’s success/failure sense) if the conversion is impossible, which lets you branch on :F(label) to handle bad input. Implicit conversion, by contrast, simply applies whenever an operator demands a particular type.

Patterns as First-Class Values

The most distinctive thing about SNOBOL’s type system is that patterns are values. ANY, SPAN, BREAK, and friends construct pattern objects you can store in variables, combine with concatenation and alternation, and pass around just like strings or numbers. This is what makes SNOBOL “pattern-directed” rather than merely “string processing.”

Create a file named patterns_as_data.sno:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
*       Build patterns and store them in variables
        VOWEL    = ANY("aeiouAEIOU")
        DIGIT    = ANY("0123456789")
        NUMBER   = SPAN("0123456789")

*       Patterns are real values - DATATYPE proves it
        OUTPUT = "type of VOWEL  = " DATATYPE(VOWEL)
        OUTPUT = "type of NUMBER = " DATATYPE(NUMBER)

*       Use a stored pattern in a match
        WORD = "snobol4"
        WORD VOWEL . V                            :F(NOMATCH)
        OUTPUT = "first vowel in 'snobol4' is: " V
        WORD NUMBER . NUM                         :F(DONE)
        OUTPUT = "trailing number is: " NUM " (" DATATYPE(NUM) ")"
NOMATCH
DONE
END

Two SNOBOL features make this example work:

  • The . operator binds the matched substring to a variable. After WORD VOWEL . V runs, V holds the single character that matched. The captured value is a STRING, even though it came from a PATTERN match.
  • Statements end with goto labels. :F(NOMATCH) jumps to the NOMATCH label if the match fails. Without goto control, a failed match would simply skip the rest of that single statement and continue with the next one.

Running with Docker

1
2
3
4
5
6
7
# Pull the SNOBOL image
docker pull esolang/snobol:latest

# Run each example
docker run --rm -v $(pwd):/app -w /app esolang/snobol:latest snobol /app/variables.sno
docker run --rm -v $(pwd):/app -w /app esolang/snobol:latest snobol /app/conversions.sno
docker run --rm -v $(pwd):/app -w /app esolang/snobol:latest snobol /app/patterns_as_data.sno

Expected Output

Running variables.sno:

COUNT    = 42
GREETING = Hello
PI       = 3.14159
EMPTY    = ''
type of COUNT    = INTEGER
type of GREETING = STRING
type of PI       = REAL
type of EMPTY    = STRING

Running conversions.sno:

'100' + 25 = 125
type of N  = INTEGER
7 + 0.5    = 7.5
type of MIX = REAL
concat result = answer=42
type of LABEL = STRING
CONVERT(3.14,'STRING') = 3.14 (STRING)
CONVERT('256','INTEGER') = 256 (INTEGER)

Running patterns_as_data.sno:

type of VOWEL  = PATTERN
type of NUMBER = PATTERN
first vowel in 'snobol4' is: o
trailing number is: 4 (STRING)

Key Concepts

  • No declarations. A variable is created on first assignment; its type comes from whatever value is bound to it.
  • Dynamic, weak typing. Operators coerce operands as needed: "100" + 25 produces an integer, "x=" 42 produces a string.
  • The null string is the universal default. Unset variables read as "", and there is no separate null/nil concept.
  • Built-in DATATYPE() returns the current type name (INTEGER, REAL, STRING, PATTERN, ARRAY, TABLE, …).
  • Numbers convert in both directions. Strings that look numeric become numbers under arithmetic; numbers become strings under concatenation. CONVERT() does it explicitly and can fail.
  • Patterns are first-class values. ANY, SPAN, BREAK and others build pattern objects you can store, combine, and reuse — this is the foundation of SNOBOL’s whole programming model.
  • Names belong in column 1; statements do not. Any line whose first character is non-blank is treated as a label, so always indent assignments and matches.

Running Today

All examples can be run using Docker:

docker pull esolang/snobol:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining