Catspeak Reference


Expressions

An expression is is a term which always produces a value, and may be composed of many sub-expressions. Most statements you would typically find in GameMaker Language (GML) appear as expressions in Catspeak. This includes Break Expressions, Return Expressions, and Continue Expressions.

For the precedence of many of these expressions, see the precedence table in the Lexical Grammar chapter.

§ Terminal Expressionstop ^

Terminal expressions are either Identifiers or literals (e.g. Numbers and Strings). These expressions have no further sub-expressions, hence the name.

Examples of terminal expressions:

Catspeak (.meow)
Copy"2036-02-03" -- string literal #BBE31A -- colour code 3.1415 -- numeric literal 'K' -- character literal 0b0011_1110 -- binary literal true -- boolean True undefined player_name -- variable identifier NaN -- not a number

§ Self Expressionstop ^

self is a special built-in value which refers to the current context a piece of code is being executed in. For example, the current GML instance, or current struct a method is bound to.

This behaves similarly to self from GML and this from JavaScript. However, unlike GML self is required in order to access instance variables:

Catspeak (.meow)
Copymy_var -- this refers to a LOCAL variable called 'my_var' self.my_var -- this refers to an INSTANCE variable on self called 'my_var' -- they are NOT the same

§ Grouped Expressionstop ^

Grouped expressions wrap a single sub-expression between ( and ), evaluating the sub-expression. This is typically used to make the precedence of the inner expression clear:

Catspeak (.meow)
Copylet a = 1 + 2 * 3 -- a = 7 let b = (1 + 2) * 3 -- b = 9

§ Array Expressionstop ^

Arrays are a sequence of comma-separated sub-expressions wrapped between [ and ]:

Catspeak (.meow)
Copylet array = [1, 2, 3]

The order of evaluation of sub-expressions [a, b, c] is left-to-right, i.e. a is evaluated first, then b, and finally c.

Whitespace does not matter, and the elements of an array can be split over multiple lines if needed (including a trailing comma on the final element if desired):

Catspeak (.meow)
Copylet names = [ "Jane", "June", "Jake", "Jade", ]

For information on how to modify the elements of an array, see the section on Accessor Expressions.

§ Struct Expressionstop ^

Structs are a sequence of comma-separated key-value pairs (key: value) wrapped between { and }:

Catspeak (.meow)
Copylet struct = { x: 1, y: 2 }

Just like Array Expressions whitespace does not matter, and the order of evaluation for sub-expressions is left-to-right.

Struct keys (seen on the left-hand-side of the : symbol) can be any valid Terminal Expression:

Catspeak (.meow)
Copylet metadata = { name : "Roxy", -- identifier 'name' used as a key "age" : 21, -- string "age" used as a key 0b0110 : 0x42, -- the binary number '0b0110' used as a key }

Any non-string key will be implicitly converted into a string before being assigned its value. For example, the identifier name becomes "name", and the binary number 0b0110 becomes "6".

Using an expression for a struct key (so-called "computed keys") is allowed, so long as the expression is surrounded with [ and ]:

Catspeak (.meow)
Copy{ ["hello" + "world"]: 123 }

If the key and value are both the same identifier { x: x, y: y }, then the key can be omitted in a short-hand initialisation syntax like { x, y }:

Catspeak (.meow)
Copylet a = 1 let b = 2 let c = 3 return { a, b, c } -- this is short for { a: a, b: b, c: c }

For information on how to modify the elements of a struct, see the section on Accessor Expressions.

§ Call Expressionstop ^

A call expression is a single sub-expression (the callee) followed by a sequence of comma-separated sub-expressions (the arguments) wrapped between ( and ):

Catspeak (.meow)
Copyshow_message("hello from Catspeak!") -- 'show_message' is the callee -- "hello from Catspeak!" is the argument

If the callee is a constructor function from GML, then it can be constructed by prefixing a call expression with the new keyword:

Catspeak (.meow)
Copylet v = new Vec2(13, 12)

If the constructor does not have any parameters, then the () can be omited:

Catspeak (.meow)
Copylet player = new Player -- instead of `new Player()`

§ Accessor Expressionstop ^

An accessor expression will allow you to access or modify the elements of an array or struct. They consist of a single sub-expression (the array or struct) followed by a sequence of comma-separated sub-expressions (the indices) wrapped between [ and ]:

Catspeak (.meow)
Copyarray[0] struct["y"]

If the index is a valid identifier, the . symbol can be used for short-hand member access vec.x, which is the same as vec["x"]. This is useful when working with structs:

Catspeak (.meow)
Copylet vec = { x: 0, y: 0 } vec.x = 10 vec.y = vec.x + 12

Depending on which side of an Assignment Operator the accessor expression is on, determines whether to read or assign a value.

§ Unary Operatorstop ^

Unary operators are made up of a single sub-expression prefixed by one of !, ~, -, or +.

They have the highest precedence of all operators, even greater than the Multiplicative Operators:

Catspeak (.meow)
Copy+a - b -- same as (+a) - b, NOT +(a - b) -a * d -- same as (-a) * d, NOT -(a * d) +-a -- not valid, unary operators cannot be chained together -- parenthesis should be used to resolve ambiguity: +(-a)
OperatorDescription
!aConverts a into a Boolean value (True or False), returning false if a evaluates to True and true if a evaluates to False.
~aConverts a into a 32-bit integer, returning a new number whose binary representation flips every 0 to a 1 and every 1 to a 0. e.g. ~0b1010 becomes 0b0101.
-aConverts a into a number, returning the number with the opposite sign. If a is positive then its negative counterpart is returned, and vice versa.
+aConverts a into a number, returning that number.

§ Multiplicative Operatorstop ^

Multiplicative operators are made up of two sub-expressions separated by one of *, /, //, or %.

They have the highest precedence of all the binary operators:

Catspeak (.meow)
Copya + b * c -- same as a + (b * c), NOT (a + b) * c a / b - c -- same as (a * b) - c, NOT a / (b - c) a * b / c * d -- same as ((a * b) / c) * d, NOT (a * b) / (c * d)
OperatorDescription
a * bConverts a and b into numbers, returning the result of multiplying them together.
a / bConverts a and b into numbers, returning the result of dividing a by b.
a // bConverts a and b into 32-bit integers, returning the quotient after dividing a by b.
a % bConverts a and b into numbers, returning the remainder after dividing a by b.

The remainder has the same sign as a; if a is positive then the remainder will be positive, and if a is negative then the remainder will be negative.

§ Additive Operatorstop ^

Additive operators are made up of two sub-expressions separated by one of + or -.

They have higher precedence than all other binary operators except the Multiplicative Operators:

Catspeak (.meow)
Copya * b + c -- same as (a * b) + c, NOT a * (b + c) a - b / c -- same as a - (b / c), NOT (a - b) / c a + b - c + d -- same as ((a + b) - c) + d, NOT (a + b) - (c + d)
OperatorDescription
a + bConverts a and b into numbers, returning their sum.
a - bConverts a and b into numbers, returning the result of subtracting b from a.

§ Bitwise Operatorstop ^

Bitwise operators are made up of two sub-expressions separated by one of &, |, ^, <<, or >>.

They have a higher precedence than Relational Operators, but a lower precedence than Additive Operators.

OperatorDescription
a & bConverts a and b to 32-bit integers, returning a new number whose binary representation is the logical AND of every bit from a and b. e.g. 0b1110 & 0b0111 becomes 0b0110, since:

1 AND 0 = 0
1 AND 1 = 1
1 AND 1 = 1
0 AND 1 = 0
a | bConverts a and b to 32-bit integers, returning a new number whose binary representation is the logical OR of every bit from a and b. e.g. 0b1110 | 0b0111 becomes 0b1111, since:

1 OR 0 = 1
1 OR 1 = 1
1 OR 1 = 1
0 OR 1 = 1
a ^ bConverts a and b to 32-bit integers, returning a new number whose binary representation is the logical XOR (exclusive OR) of every bit from a and b. e.g. 0b1110 ^ 0b0111 becomes 0b1001, since:

1 XOR 0 = 1
1 XOR 1 = 0
1 XOR 1 = 0
0 XOR 1 = 1
a << bConverts a and b to 32-bit integers, shifting the binary representation of the value a to the left by b-many bits. e.g. b0110 << 1 becomes 0b1100.

This has the effect of multiplying a by 2b.
a >> bConverts a and b to 32-bit integers, shifting the binary representation of the value a to the right by b-many bits.

§ Relational Operatorstop ^

Relational operators are made up of two sub-expressions separated by one of <, <=, >, or >=.

They have a higher precedence than Equality Operators, but a lower precedence than Bitwise Operators:

Catspeak (.meow)
Copya * b < c -- same as (a * b) < c, NOT a * (b < c) a >= b / c -- same as a >= (b / c), NOT (a >= b) / c a < b < c -- same as (a < b) < c, NOT (a < b) and (b < c)
OperatorDescription
a < bConverts a and b into numbers, returning whether a is less than b.
a <= bConverts a and b into numbers, returning whether a is less than or equal to b
a > bConverts a and b into numbers, returning whether a is greater than b.
a >= bConverts a and b into numbers, returning whether a is greater than or equal to b

§ Equality Operatorstop ^

Equality operators are made up of two sub-expressions separated by one of == or !=.

They have a higher precedence than Bitwise Operators, but a lower precedence than Relational Operators:

Catspeak (.meow)
Copya * b == c -- same as (a * b) == c, NOT a * (b == c) a != b < c -- same as a != (b < c), NOT (a != b) < c a == b == c -- same as ((a == b) == c), NOT (a == b) and (b == c)
OperatorDescription
a == bCompares the values of a and b, returning true if they are equal.

Equivalence is determined by the behaviour of GML equality.
a != bCompares the values of a and b, returning true if they are not equal.

§ Pipe Operatorstop ^

Pipe operators are made up of two sub-expressions separated by one of <| or |>.

They have a higher precedence than Logical Operators, but a lower precedence than Bitwise Operators.

OperatorDescription
a <| bSyntactic sugar for a function call a(b).
a |> bSyntactic sugar for a function call b(a).

§ Logical Operatorstop ^

Logical operators are made up of two sub-expressions separated by one of and, or, or xor.

They have a higher precedence than Assignment Operators, but a lower precedence than Pipe Operators. Additionally, the and operator takes precedence over or and xor:

Catspeak (.meow)
Copya and b or c -- same as (a and b) or c, NOT a and (b or c) a or b and c -- same as a or (b and c), NOT (a or b) and c a and b or c and d -- same as (a and b) or (c and d), NOT ((a and b) or c) and d
OperatorDescription
a and bConverts a to a Boolean value, returning b if a evaluates to True. Returns a otherwise.

Does not evaluate b if a evaluates to False. This is called short-circuit evaluation.
a or bConverts a to a Boolean value, returning b if a evaluates to False. Returns a otherwise.

Just like the and operator, b is not evaluated if a evaluates to True.
a xor bConverts a and b to a Boolean values, returning true if the values of a and b are different. Returns false otherwise.

Unlike and and or, the xor operator is not short-circuiting.

§ Assignment Operatorstop ^

Assignment operators are made up of two sub-expressions separated by one of =, *=, /=, -=, or +=.

They have the lowest precedence of all binary operators:

Catspeak (.meow)
Copya = b = c -- same as a = (b = c), NOT a = c; b = c -- the result of an assignment operator is always 'undefined' let a let b = (a = 2) -- a is 2, b is undefined

The value of the right-hand-side expression will be written to the left-hand-side assignment target expression.

OperatorDescription
a = bWrites the value of b to a
a *= bWrites the value of a * b to a
a /= bWrites the value of a / b to a
a -= bWrites the value of a - b to a
a += bWrites the value of a + b to a

Valid assignment targets for the expression a include Identifiers (if they refer to a writable variable), and Accessor Expressions.

§ If Expressionstop ^

Simple if expressions start with the if keyword, followed by a single sub-expression (the condition), and finally a block containing code to run when the condition evaluates to True:

Catspeak (.meow)
Copyif a > b { show_message("a is greater than b") }

An optional else keyword can be used, followed by another block or if expression, to handle the case where the condition evaluates to False:

Catspeak (.meow)
Copyif a > b { show_message("a is greater than b") } else if a < b { show_message("a is less than b") } else { show_message("a and b are equal") }

Unlike GML, only a block or another if expression can follow the else keyword. So shortcuts like else while or else do are not allowed.

As mentioned on the page about Statements, almost all statements found in GML are allowed to be used as expressions in Catspeak. This allows Catspeak to imitate ternary operators from GML:

Catspeak (.meow)
Copylet max = if a > b { a } else { b } -- instead of a > b ? a : e

§ While Loopstop ^

While loops start with the while keyword, followed by a single sub-expression (the condition), and finally a block containing code to run whilst the condition evaluates to True:

Catspeak (.meow)
Copy-- counts down from 100 let n = 100 while n > 0 { show_message(n) n -= 1 }

Continue Expressions and Break Expressions are also valid from within while loops, allowing for a loop iteration to be skipped using continue or to break out of the loop early using break.

§ With Loopstop ^

With loops behave similarly to GML. They start with the with keyword, followed by a single sub-expression (the context), and finally a block containing code to run in that context. This basically modifies the value returned by Self Expressions to instead return the context:

Catspeak (.meow)
Copylet vec = { x : 1, y : 2 } with vec { show_message(self.x * self.y) -- 'self' here refers to the 'vec' struct }

Unlike GML, the self keyword is not optional.

Continue Expressions and Break Expressions are also valid from within while loops, allowing for a loop iteration to be skipped using continue or to break out of the loop early using break. These only really become useful when using an GML object asset as the context, as it will loop over every active instance of that object that exists in the current room.

§ Continue Expressionstop ^

The continue expression will immediately jump to the start of the current While Loop or With Loop, entering the next iteration of the loop if one exists.

Catspeak (.meow)
Copylet n = 10 while n > 0 { n -= 1 continue -- will skip the remainder of the loop n += 1 -- this line will never be evaluated }

§ Break Expressionstop ^

The break expression will immediately terminate the execution of the current While Loop or With Loop, skipping the rest of the current iteration.

Catspeak (.meow)
Copylet n = 0 while true { n += 1 if n > 10 { -- break out of the loop when the number is greater than 10 break } } show_message(n) -- n = 11

Break expressions start with the break keyword, optionally followed by a single sub-expression. If this sub-expression is supplied then then it is used as the result of loop it broke out of:

Catspeak (.meow)
Copy-- loop until some random number greater than 75 is generated, then -- store it inside of the 'random_result' variable let random_result = while true { let n = irandom_range(0, 100) if n > 75 { break n } }

§ Block Expressionstop ^

Blocks in Catspeak are made up of a sequence of Statements wrapped between { and }. Many expressions (such as If Expressions, While Loops, and Function Expressions) use blocks for the purposes of control flow.

Block expressions start with the do keyword (not to be confused with do loops from GML) followed by a block:

Catspeak (.meow)
Copylet r = do { let a = 1 let b = 2 a + b -- a + b is used as the value of the whole block expression } -- r is 3 here, because a + b = 3

Each statement in a block is evaluated in sequence from top-to-bottom, with the last value (if one exists) being used as the result. If the last statement of a block expression is not an expression, then the result of the block is undefined.

Unlike other expressions which use blocks, block expressions have no control flow mechanism. In many cases, using a block expression or not has no real impact on the behaviour of the code:

Catspeak (.meow)
Copy-- with a block expression do { let message = "five pebbles" something() something_else(message) } another_thing() -- without a block expression let message = "five pebbles" something() something_else(message) another_thing()

However, reiterating what was said in the section on Let Statements, all local variables defined within a block will become inaccessible after the block has ended. This is different from GML variables, whose definitions are hoisted.

§ Function Expressionstop ^

Function expressions start with the fun keyword, followed by an optional sequence of comma-separated identifiers (the parameters) between ( and ), and finally a single block containing the function body:

Catspeak (.meow)
Copylet add = fun (a, b) { return a + b } show_message(add(1, 2)) -- prints '3'

Unlike GML, all functions in Catspeak are anonymous. There is no such thing as a "named function" (i.e. function foo() { } in GML).

§ Return Expressionstop ^

Return expressions start with the return keyword, optionally followed by a single sub-expression (the return value). A return expression will immediately terminate the current function call, using the return value as the result of the function call.

Catspeak (.meow)
Copylet max = fun (a, b) { if (a > b) { return a } return b } let max_of_12_9 = max(12, 9) -- 12 let max_of_5_8 = max(5, 8) -- 8

If no return value exists, then undefined is returned instead.

§ Match Expressionstop ^

Match expressions start with the match keyword, followed by a single sub-expression (the condition), and then zero or more match cases wrapped between { and }:

Catspeak (.meow)
Copylet colour match player { case "Dave" { colour = #e00707 } case "Rose" { colour = #b536da } case "Dash" { colour = #f2a400 } case "Roxy" { colour = #ff6ff2 } else { show_message("invalid player") } }

A match case can be one of two things:

  • A sequence of cases starting with the case keyword, followed by a single sub-expression representing the expected value of the match condition.

  • Or the else keyword, representing the default case.

Every match case ends in a block containing code to run if that case is satisfies. The default case only runs its code if no other case is satisfied.

For most intents and purposes, match expressions are syntactic sugar for If Expressions:

Catspeak (.meow)
Copy-- match expression let result = match chr { case 'A' { some_a() } case 'B' { some_b() } case 'C' { some_c() } else { some_else() } } -- equivalent if expression let result = if chr == 'A' { some_a() } else if chr == 'B' { some_b() } else if chr == 'C' { some_c() } else { some_else() }