Glue

A small language where spacing means something.

Hello, world

say writes a line of output. Throughout these pages, lines beginning with >> show the program's expected output — they are not part of the program.

say "Hello, world"
>> Hello, world

Strings interpolate anything between braces — not just variable names, but any expression:

my who ("world")
say "Hello, {who} — and 2 + 2 is {2 + 2}"
>> Hello, world — and 2 + 2 is 4

The two whitespace rules

Glue's identity rests on two rules about whitespace. Once you know them, most of the language follows.

1. Blocks are indentation — tabs only, and the head line ends in :

Like Python, a block is marked by indenting, not by braces or end. Unlike Python, indentation must be tab characters: one tab per level, and a space at the start of a line is a syntax error. There is exactly one way to indent.

if age < 18:
	say "minor"

The line that introduces a block ends in a glued : — and a line ending in : requires an indented block under it. Block structure is therefore stated twice, by the colon and by the indentation, so an indentation slip is an error message instead of a silently different program.

2. The adjacency rule

Horizontal spacing matters too. A symbol standing alone (space on both sides) is an operator; a symbol glued to its neighbour is part of the token. The same characters mean different things depending on spacing:

You writeGlue reads
total(x)call the function total with argument x
total (x)assign the value of x to the variable total
a-bone identifier named a-b (hyphens are legal inside names)
a - bsubtraction
-anegation of a

Isn't that fragile? One space changing the meaning sounds dangerous, so the language compensates by being loud: when spacing doesn't produce a legal reading, that's a hard compile error, not a guess. Much of the design deliberately arranges for the mistyped version of a line to be illegal rather than quietly different.

Variables

Two things surprise most newcomers. First, a variable must be declared the first time it is bound — my declares it in the current block. Second, there is no = assignment. Assignment is the name followed by a spaced parenthesis group: name (value).

# declare count, starting at 0
my count (0)
# reassign: name, space, (value)
count (count + 1)
say count
>> 1

The = sign is pure equality, as in mathematics:

if count = 1:
	say "count is one"
>> count is one

Assigning to a name that was never declared is a compile error, which catches typos at the moment they happen:

my total (5)
# ERROR: 'totle' was never declared
totle (total + 1)

Besides my there are up (declare in the enclosing block) and global (file scope). Declaring a name that is already visible is also an error — no shadowing, ever.

Strings

Double quotes interpolate; single quotes are raw:

say 'no {interpolation} in raw strings'
>> no {interpolation} in raw strings

There are no backslash escape sequences. A backslash in a string is just a backslash. Special characters are ordinary named constants that you interpolate:

say "first line{NL}second line"
>> first line
>> second line

A list won't silently flatten into a string — you must say how to join it:

my parts (["alpha", "beta", "gamma"])
say "parts: {parts.join(', ')}"
>> parts: alpha, beta, gamma

(There is no string concatenation operator at all. Interpolation and .join cover it.)

Making decisions

if you already know. or at the start of a line is “else if”, and else is the fallback:

if age < 18:
	say "minor"
or age < 21:
	say "US minor"
else:
	say "adult"

A short statement can carry its condition at the end instead:

say "quiet in here" if volume < 3

When a decision is really a table, write it as one. choose tries each row top to bottom, and the first condition that holds supplies the value — here assigned into advice:

my advice (choose:
	age < 18	"Too young to drink"
	age < 21	"Too young to drink in the US"
	default 	"Legal drinking age"
)
say advice

Loops

for over a range (.. is inclusive, ..< excludes the end):

for i (1 .. 5):
	say "beat {i}"
>> beat 1
>> beat 2
>> beat 3
>> beat 4
>> beat 5

for over a list:

my scores ([70, 85, 90])
my total (0)
for s (scores):
	total (total + s)
say "total {total}"
>> total 245

And while, for as long as a condition holds:

my n (1)
while n < 100:
	n (n * 2)
say n
>> 128

Your first function

A function definition is the function's name with a glued leading dot — no def or function keyword. Parameters follow bare, and the head line ends in : like every line that introduces a block:

.greet "who", punct ('!'):
	say "Hello, {who}{punct}"

greet old friend of mine
>> Hello, old friend of mine!

Three things to notice. The quoted parameter "who" is an implicit-string parameter, so the call site may pass bare text — no quotes needed. punct ('!') gives a default value. And the call is just the bare name greet followed by its arguments.

Values come back with an explicit return — always. A final parameter spelled rest[] collects any number of remaining arguments:

.sum first, rest[]:
	my t (first)
	for v (rest):
		t (t + v)
	return t

say sum(1, 2, 3)
>> 6

The nested call glues its parens: a bare argument list belongs to the statement head alone — sum 1, 2, 3 on its own line is fine, but inside another statement's arguments the parens say where the inner list ends.

A complete little program

Can you read this? It defines a classifier with a {verbose} option, collects a label for each age with a value-producing loop, and reports:

.classify {verbose} age {int}:
	say "checking {age}" if verbose?
	return (choose:
		age < 18	"minor"
		age < 21	"US minor"
		default 	"adult"
	)

my ages ([12, 19, 30])

my labels (for {list} age (ages):
	classify {verbose} age
)

say "{labels.count} people: {labels.join(', ')}"
>> checking 12
>> checking 19
>> checking 30
>> 3 people: minor, US minor, adult
  1. {verbose} in braces is an option — braces after a head always mean named options, at definitions and at call sites alike. Inside the body, the option is an ordinary variable; verbose? asks whether it was set.
  2. age {int} declares a typed parameter.
  3. for {list} is a loop used as a value: it collects each iteration's result into a list.
  4. Everything — keyword, definition, call — has the same statement shape: head {options} arguments (value), then : and an indented block. Learn the shape once and you have read the whole language.

Ready for more? — the feature overview →