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