Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reserved words

ALLOW · AS · ASYNC · ATOMIC · BIND · BLOCKBIND · BREAK · CASE · CONST · CONTINUE · DO · ELSE · END · ENUM · FALSE · FUNCTION · GIVES · HANDLERBIND · IF · IMPORT · LOCAL · LOOP · NAMESPACE · NULL · RETURN · REWRITE · SCOPEBIND · SELECT CASE · SELF · TEXTBIND · THEN · TRUE · TYPE · UNTIL · WEND · WHILE


ALLOW

Part of every kind of BIND, and of FUNCTION: lists the modifier functions that may follow !! after the keyword, or after a call of the function. I turn away any other modifier when the script loads, before anything has run.

Name a namespace instead and I allow every function declared in it. You still write each one by its own short name after !!, and that name stays free for your variables everywhere else.

On a FUNCTION, a parameter’s name with ... after it takes the rest: any other word after !! goes into that parameter, as a map of each word to the value it was given. That’s for words only the function can know, so I can’t check them for you before the script runs.

BIND KEYWORD function ALLOW(modifier, modifier)
BIND KEYWORD function ALLOW(Namespace)
FUNCTION name(parameters) ALLOW(Namespace, parameter...)

Examples

BIND DRAW LINE line ALLOW(thickness, dashed)
BIND DRAW LINE line ALLOW(Draw)

AS

Names the type of a field, a parameter or a function’s result. The name has to be a built-in type or a TYPE you’ve declared. It tells readers — and the documentation — what a value is meant to be; I don’t check it while the script runs.

name AS type

Example

FUNCTION heal(who AS Player, amount AS INTEGER = 10) AS INTEGER

ASYNC

Put this in front of a call and I’ll start that call on its own thread and hand you a future straight away, instead of making you wait. The function is declared as usual, so you can call the same one either way. When you want the answer, ask for it with RESOLVE.

A future isn’t the answer yet, so I’ll stop you if you use one as an ordinary value before resolving it.

Add !! modifiers and the call isn’t started right away. I work out its arguments on the spot, then hand the call to the host, and the host starts it when the modifiers say — later, or again and again. That’s how timers are made. Which modifiers there are is up to the host; I have no clock of my own! You still get a future straight away: for a call that runs once it becomes the answer, and for one that repeats it’s what you hold on to so you can stop it.

ASYNC function(arguments)
ASYNC function(arguments) !! modifiers

Examples

first = ASYNC load_level(1)
second = ASYNC load_level(2)
levels = RESOLVE(first, second)
ASYNC show_message("time's up") !! after(30000)
ticking = ASYNC world_step() !! every(100)

ATOMIC

Marks a shared variable so that changing it is one step, however many threads are at it. Without the mark, two threads running total = total + 1 at the same moment can both read the old value, and one of the additions is lost. With it, I let one thread finish its change before the next one starts — nothing gets lost.

The mark is on the name, for the whole program, so write the ATOMIC line once at the top of a file and assign to the variable as usual everywhere else. Changing a part of it, like scores.ada = scores.ada + 1, is one step too. Reading it is never held up.

One rule: a line that changes an atomic variable using its own value can’t call a function, because every other thread that wants the variable is waiting for that line. Work the value out on the line before.

ATOMIC name = value

Examples

ATOMIC total = 0
FUNCTION count(amount)
    total = total + amount
END FUNCTION
ATOMIC total = 0
bonus = slow_sum(1, 2)
total = total + bonus

BIND

Gives a function a keyword of one to four words. After that you write the keyword without parentheses, followed by its arguments. This is how the language grows — even PRINT is just a keyword somebody bound!

With an argument list, the names tell me which of the function’s parameters the keyword’s arguments fill, in the function’s order, and a constant is always passed for that parameter. ALLOW lists the modifier functions the keyword accepts after !!.

Every function is already a one-word keyword under its own name, so you only need BIND for a different name, more than one word, fixed arguments or modifiers.

BIND ASYNC is the one special case. It names the function I hand an ASYNC call to when the call has !! modifiers, and ALLOW lists those modifiers. The call arrives as that function’s last parameter.

BIND KEYWORD function
BIND KEYWORD WORD function(arguments) ALLOW(modifiers)
BIND ASYNC function ALLOW(modifiers)

Examples

BIND SHOUT say
BIND TURN LEFT rotate(sprite, degrees, -1)
BIND DRAW LINE line ALLOW(thickness, dashed)
BIND ASYNC schedule ALLOW(after, every)

BLOCKBIND

Gives a function a keyword that opens a block I run over and over — a loop. I call the function once, and it tells me what to step through: an array, a range, a map, or an iterator from the host. Then I run the block once for each item. Close it with END and the keyword, or with NEXT, the way BASIC always has.

Names before an = in the block’s first line receive each item. With one name I give you the item (or the key of a map); with two, the position and the item (or the key and the value).

For a block that runs once, use SCOPEBIND. For one that’s kept and called later, use HANDLERBIND.

BLOCKBIND KEYWORD function ALLOW(modifiers)

Example

BLOCKBIND FOR each ALLOW(step)
FOR index, item = inventory
    PRINT index, item
END FOR

BREAK

Gets me out of the innermost loop or block straight away. It only makes sense inside one, so I’ll stop you if there’s no loop around it.

BREAK

Example

FOR n = numbers
    IF n < 0 THEN BREAK
END FOR

CASE

Starts one case of a SELECT CASE, and says what fits it. List as many as you like with commas: a value fits when it’s equal, low TO high fits anything from one end to the other, and IS with a comparison fits whatever passes it. CASE ELSE fits everything, so it has to come last.

CASE value, ...
CASE low TO high
CASE IS comparison value
CASE ELSE

Example

CASE 1, 3, 5 TO 9, IS > 100

CONST

Gives a shared variable its value, once. After that I won’t let anything assign to it, or to a part of it, and I say so when the script loads rather than when the line runs. Use it for the numbers and names a program is built around, and inside a NAMESPACE to keep a set of them together.

It goes at the top of a file or in a NAMESPACE, like any shared variable. The value can be any expression; it’s worked out when the line runs.

CONST name = value

Examples

CONST gravity = 9.8
CONST start = (160, 120)
NAMESPACE State
    CONST idle = 0
    CONST walking = 1
    CONST jumping = 2
END NAMESPACE
IF mode = State.jumping THEN PRINT "whee"

CONTINUE

Skips the rest of this pass and starts the next one. Like BREAK, it needs a loop around it.

CONTINUE

Example

FOR n = numbers
    IF n = 0 THEN CONTINUE
    PRINT 100 / n
END FOR

DO

Repeats a block. End it with a plain LOOP and I’ll keep going until a BREAK. Put a test on the LOOP line and I’ll make it after each pass, so the block always runs at least once; put it on the DO line and I’ll make it before each pass, so the block may not run at all. WHILE keeps me going while the condition is true, UNTIL while it’s false. One test to a loop, please!

DO
    ...
LOOP
DO
    ...
LOOP WHILE condition
DO
    ...
LOOP UNTIL condition
DO WHILE condition
    ...
LOOP
DO UNTIL condition
    ...
LOOP

Examples

DO
    turn = turn + 1
LOOP WHILE turn < 10
DO UNTIL lives = 0
    lives = lives - 1
LOOP

ELSE

Starts the part of an IF that I run when the condition is false. ELSE IF has me test another condition first. A plain ELSE has to come last. On a one-line IF it goes on the same line, after the statement.

ELSE
ELSE IF condition
IF condition THEN statement ELSE statement

Examples

IF hp > 50
    PRINT "fine"
ELSE IF hp > 0
    PRINT "hurt"
ELSE
    PRINT "down"
END IF
IF hp > 0 THEN PRINT "still up" ELSE PRINT "down"

END

Closes a block. It always names what it closes — that’s how I can point at a missing or mismatched one right where it happens, not fifty lines later.

A loop block has a second way to close: NEXT, on its own or followed by the loop’s variable. If you name the variable I check it’s the right loop. NEXT isn’t one of my reserved words, so outside a loop you can still use next as a name of your own.

END IF
END FUNCTION
END keyword

Examples

FOR i = 1 ... 3
    PRINT i
END FOR
FOR i = 1 ... 3
    PRINT i
NEXT i

ENUM

Names a run of numbers. Each name inside is a constant, reached through the ENUM’s own name. The first is 0 and each one after is one more than the one before, unless you give it a value — and then the counting carries on from there. Put them one to a line, or several with commas. enum isn’t a reserved word: it only means this in front of a name.

ENUM name
    member
    member = value
    ...
END ENUM

Examples

ENUM Mode
    idle
    playing
    paused = 10
    over
END ENUM
PRINT Mode.playing, Mode.over
ENUM Flag
    solid = 1, sticky = 2, deadly = 4
END ENUM
PRINT Flag.solid 
 Flag.deadly

FALSE

The integer 0, with a friendlier name.

Example

running = FALSE

FUNCTION

Teaches me a new function. A parameter may have a type and a default; one with no default has to be given. End the last parameter with ..., as in name..., and I’ll collect any extra arguments into an array for you.

Declare it as Type::Name and it’s a method of that type, called with a dot. A function declared inside another can use the outer function’s parameters and locals — I hand it a copy of them at the moment its name is written.

Put a /// comment above a function and that becomes its documentation.

Leave the name off and it’s a function written right where it’s used: a value like any other, to keep in a variable or hand to another function. It gets a copy of the locals around it at that moment, and inside a method it keeps SELF too. For one that’s a single expression, => is shorter.

FUNCTION name(parameters) AS type
    ...
END FUNCTION
FUNCTION Type::name(parameters)
FUNCTION(parameters)
    ...
END FUNCTION

Examples

FUNCTION greet(name, greeting = "hello")
    RETURN greeting + ", " + name
END FUNCTION
FUNCTION Player::Heal(amount)
    SELF.hp = SELF.hp + amount
END FUNCTION
best = sort(scores, FUNCTION(a, b)
    IF a.level = b.level THEN RETURN a.name < b.name
    RETURN a.level > b.level
END FUNCTION)

GIVES

Part of HANDLERBIND: names the values a handler block is given when it’s called, in order. Inside the block they’re ordinary variables. If the caller leaves one out, it’s NULL.

HANDLERBIND KEYWORD function GIVES(name, name)

Example

HANDLERBIND ON HIT on_hit GIVES(me, other)
ON HIT
    PRINT me, "bumped into", other
END ON HIT

HANDLERBIND

Gives a function a keyword that opens a block I don’t run. I wrap the block up as a function and hand it to the bound function as its last parameter — the keyword’s own arguments fill the ones before it. Whoever holds it can call it later, as often as they like: when a key is pressed, when two sprites collide.

GIVES names the values the block is handed each time it’s called. Inside the block, RETURN gives a value back to the caller. The block can use the shared variables as usual, and it gets a copy of the locals around it, taken when the keyword runs — it can read those, but changing them doesn’t change the originals.

HANDLERBIND KEYWORD function GIVES(names) ALLOW(modifiers)

Example

HANDLERBIND ON KEY on_key GIVES(key)
ON KEY "space"
    PRINT "jump"
END ON KEY

IF

Runs code only when a condition is true. To me, NULL, 0, 0.0 and errors are false; everything else is true — yes, even an empty string.

The one-line form takes THEN and a single statement, and can go on with ELSE and another. The block form has no THEN and ends with END IF.

IF condition THEN statement
IF condition THEN statement ELSE statement
IF condition
    ...
ELSE IF condition
    ...
ELSE
    ...
END IF

Examples

IF hp <= 0 THEN PRINT "game over"
IF hp > 50 THEN PRINT "fine" ELSE PRINT "hurt"
IF score > best
    best = score
ELSE
    PRINT "not this time"
END IF

IMPORT

Hands me another file to read. Its functions, types and keywords become yours to use, and I run its top-level statements the first time it’s imported. I only ever load a file once, however many files ask for it.

I start looking from the folder of the file doing the importing, and I’ll add .crash if you leave it off. IMPORT has to stand on its own, outside every block and function.

IMPORT "path"

Example

IMPORT "lib/geometry"

LOCAL

Makes a variable that belongs to the function it’s in. Without LOCAL, assigning to a name inside a function changes the variable the whole program shares — handy, but easy to do by accident! At the top level of a file, a local belongs to that file.

LOCAL name = value

Example

FUNCTION total(numbers)
    LOCAL sum = 0
    FOR n = numbers
        sum = sum + n
    END FOR
    RETURN sum
END FUNCTION

LOOP

Ends a DO block and sends me back to its start. With WHILE, I only go back while the condition is true; with UNTIL, only while it’s false.

LOOP
LOOP WHILE condition
LOOP UNTIL condition

Example

DO
    IF done() THEN BREAK
LOOP

NAMESPACE

Gathers functions, types and variables under one name, so yours never bump into somebody else’s. From outside, reach them with the name and a dot. Inside, they can use each other’s names directly.

A variable you assign inside a namespace, outside its functions, belongs to the namespace — it isn’t a global, so Config.speed and a global speed are two different things. A function inside changes the nearest variable of that name: its own namespace first, then the ones around it, then the global.

You can add a function to a namespace from outside by writing its full name, like FUNCTION Geometry.area(shape).

NAMESPACE name
    ...
END NAMESPACE

Examples

NAMESPACE Geometry
    FUNCTION midpoint(a, b)
        RETURN (a + b) / 2
    END FUNCTION
END NAMESPACE
PRINT Geometry.midpoint((0, 0), (4, 6))
NAMESPACE Config
    speed = 5
    FUNCTION faster()
        speed = speed + 1
    END FUNCTION
END NAMESPACE
Config.faster()
PRINT Config.speed

NULL

No value at all. It’s false, and equal only to itself. I give you NULL for a map key that isn’t there, and from a function that returns nothing.

Example

IF scores("linus") = NULL THEN PRINT "no score yet"

RETURN

Leaves a function, giving back a value if you wrote one. A function that reaches its end without a RETURN gives NULL. Outside a function, it ends the program.

RETURN
RETURN value

Example

FUNCTION double(n)
    RETURN n * 2
END FUNCTION

REWRITE

Tells me another way a statement may be written, and what to read it as. The words and marks of the shape have to be there; each name in braces stands for whatever is written in its place, and can be used after the =>. I rewrite a line that fits before I look at anything else, so the line keeps its number. Rules are in force in every file of a program once one of them has the rule, so a library of them can be imported by scripts written the older way. REWRITE is only a word at the start of a line like this.

REWRITE shape => what it becomes

Example

REWRITE SHOW {first}; {second} => PRINT {first}, {second}
SHOW "lives", 3 - 1; "left"

SCOPEBIND

Gives a function a keyword that opens a block I run exactly once — for when everything inside belongs to one thing, like a layer to draw on or a behavior being put together. I call the function when the block opens, and what it returns is the block’s value. Name a second function after END and I hand it that value when the block finishes, so it can tidy up or register what was built.

Code inside the block gets the value with SCOPE("KEYWORD"), even from a function the block calls. A name before an = in the block’s first line receives it too.

The block isn’t a loop, so BREAK and CONTINUE inside it belong to the loop around it. I still call the closing function when you leave that way, or with RETURN. The one time I don’t is when the script stops with an error — nothing half-finished gets registered.

SCOPEBIND KEYWORD open_function END close_function ALLOW(modifiers)

Example

SCOPEBIND RECIPE start_recipe END save_recipe
RECIPE "soup"
    ADD "water"
    IF spicy THEN ADD "chili"
END RECIPE

SELECT CASE

Picks which lines to run by a value. I work the value out once, then go down the CASE lines and run the block under the first one that fits — only that one. If none fits I run the block under CASE ELSE, or nothing when there isn’t one. SELECT and CASE aren’t reserved words: they only mean this when written this way.

SELECT CASE value
    CASE match, ...
        ...
    CASE ELSE
        ...
END SELECT

Example

SELECT CASE score
    CASE 100
        PRINT "perfect"
    CASE 90 TO 99
        PRINT "great"
    CASE IS < 50
        PRINT "oops"
    CASE ELSE
        PRINT "fine"
END SELECT

SELF

Inside a method, the object the method was called on. Outside a method it doesn’t mean anything, so I’ll stop you there. A function declared inside a method doesn’t get SELF — pass the object to it if it needs one.

SELF.field
SELF.method(arguments)

Example

FUNCTION Player::Hurt(amount)
    SELF.hp = SELF.hp - amount
    RETURN SELF.hp
END FUNCTION

TEXTBIND

Gives a function a keyword that opens a block of text. I don’t read the lines inside at all: they can be SQL, a shader, a level drawn in characters, anything. I hand them to the bound function as one string, as its last parameter — the keyword’s own arguments fill the ones before it. Only a line that is END and the keyword’s own words closes the block.

The text arrives as you wrote it, moved left as far as its least indented line. AS "kind" says what the text is, so an editor that knows that kind can color it.

The keyword has to start a line of its own. A script can bind one too: put the TEXTBIND line above the first block that uses it, or in a file you import.

TEXTBIND KEYWORD function AS "kind" ALLOW(modifiers)

Example

TEXTBIND LEVEL build_level AS "map"
LEVEL "cave"
    #####
    #@..#
    #####
END LEVEL

THEN

Sits between the condition and the statement in a one-line IF. A block IF doesn’t need it.

IF condition THEN statement
IF condition THEN statement ELSE statement

Example

IF lives = 0 THEN RETURN

TRUE

The integer 1, with a friendlier name.

Example

running = TRUE

TYPE

Tells me about a kind of object with a fixed set of fields. The type’s name becomes a function that makes one: call it with nothing for the defaults, or with a map to set some fields yourself.

If you don’t give a field a default, I start it as an empty value of its type: 0, 0.0, "", an empty array or map, (0, 0) for a COORDINATE — and for a field that is another type, a fresh object of that type with its own defaults. (A type that holds itself starts NULL there, or I’d never finish making one!)

Give a map for a field that is another type and I turn it into that type for you, filling in whatever the map leaves out. So nested JSON becomes nested objects in one go.

I’ll stop you if you read or write a field the type doesn’t have — that’s nearly always a typo.

TYPE name
    field AS type
    field AS type = default
END TYPE

Examples

TYPE Player
    name AS STRING
    hp AS INTEGER = 100
END TYPE
hero = Player({name: "ada"})
TYPE Team
    captain AS Player
END TYPE
team = Team({captain: {name: "grace"}})
PRINT team.captain.hp

UNTIL

Stands where WHILE can on a DO or LOOP line, and means the opposite: keep going while the condition is false. It’s only a word there, so you can still have a variable called until, and a keyword like DELAY UNTIL.

DO UNTIL condition
    ...
LOOP
DO
    ...
LOOP UNTIL condition

Example

DO
    roll = roll + 1
LOOP UNTIL roll = 6

WEND

Closes a WHILE block, exactly as END WHILE does. It’s only a word there: anywhere else wend is a name like any other.

WHILE condition
    ...
WEND

Example

WHILE lives > 0
    lives = lives - 1
WEND

WHILE

Repeats a block for as long as a condition is true. I test the condition before each pass, so the block may not run at all.

WHILE condition
    ...
END WHILE

Example

WHILE lives > 0
    lives = lives - 1
END WHILE