Variables

A variable is a temporary value. Think of it like a box that can hold a piece of paper with something on it. A variable can hold any datatype.

To define a variable, use the following syntax:
x = 0
This defines a variable named x. The value of x can be updated throughout the program like so:
x = 0
print(x) // 0
x = 2
print(x) // 2
Or used in expressions:
x = 5
print(x * 3) //15
You can view the types a variable can hold in the docs for types.

Constants

Variables can be also declared as constant, using the const keyword before the declaration.
Const variables cannot be reassigned.
const pi = 3.14
pi = 4 // invalid - raises an error
These are useful for defining values that do not change during the execution of a program, but would be time consuming to write out every time (or update their value manually).
Variables that are assigned to be equal to a constant can be modified without effect:
const pi = 3.14

my_pi = pi
my_pi = 4 // ok

print(my_pi) // 4
print(pi) // 3.14, remains unchanged
For more on constants, see the dedicated section in arrays.

Operators

An operation that can be performed on two expressions. Each operator is either:
  • Binary (meaning two, not related to binary numbers) - takes two arguments, in the form a op b. Example include + and ==
  • Unary - takes just the one argument, in the form op x. Examples include - and NOT
Note that - is the only operator that is both unary and binary.

Binary operators

Operator Description Valid types Returns
. Access an object member String, Array, File Any
^ Raise one number to the power of another. Throws an error if a negative number is raised to a fractional power Number Real
DIV Find the whole part of the division of two numbers Number Integer
MOD Find the remainder of the division of two numbers Number Number
* Multiply two numbers Number Number
/ Divide two numbers. Throws an error if the second operand is zero, and always yields a float Number Real
+ Add two numbers or concatenate two stings String, Number String, Number
- Subtract two numbers Number Number
<= Less than or equal to comparison. Return true if the left operand is less than or equal to the right. Number Boolean
>= Greater than or equal to comparison. Return true if the left operand is greater than or equal to the right. Number Boolean
< Less than comparison. Return true if the left operand is less than or equal to the right. Number Boolean
> Greater than or equal to comparison. Return true if the left operand is greater than or equal to the right. Number Boolean
== Equality check. Returns true if the two operands are equal. Reference types like objects, files and arrays will only equal the original instance, and operands of different types will never equal each other. Any Boolean
!= Inquality check. Returns true if the two operands are not equal. Reference types like objects, files and arrays will only equal the original instance, and operands of different types will never equal each other. Any Boolean
AND Boolean AND. Yields true if both operands are true, otherwise false Boolean Boolean
OR Boolean OR. Yields true if either operand is true, otherwise false Boolean Boolean
XOR Boolean XOR. Yields true if one operand is true and the other is false, otherwise false Boolean Boolean

Unary operators

Operator Description Valid types Returns
- Negate a number Number Number
NOT Boolean NOT. Yields true if the operand is false, and false if the operand is true Boolean Boolean

Precedence

Precedence controls which order to perform operations in. You can see BODMAS in effect in the middle of the table.
Operator
.
unary -
^
DIV, MOD
*, /
+, -
<, >, <=, >=
==, !=
NOT
AND, OR, XOR

Operators will "consume" everything above them. For example, in 3 * 2 + 1 * 4, the + consumes both * to yield (3 * 2) + (1 * 4). However, in 3 + 2 * 1 + 4, the * cannot consume either +, leaving 3 + (2 * 1) + 4. Higher operators will "consume" less than lower ones, and thus are "tighter". You can use parentheses to override this ordering.

Some operators are placed on the same line. This is because neither takes priority over the other - they are evaluated in the order they come. For example, 3 - 2 + 1 becomes (3 - 2) + 1, as the - is encountered first.

In the exam, do not leave precedence to chance. Even if it is obvious what is evaluated first, there is no harm in putting brackets in to clarify your intent.

Selection

There are two selection constructs in the language: if and switch.

if statements

If statements take a boolean expression and run the code they are given if it is true:
number = input("guess my favourite number")
if number == "3" then
    // this only runs when number == "3" is true
    print("correct!")
endif
They can optionally be extended to have an else clause. This will run if the expression was false:
number = input("guess my favourite number")
if number == "3" then
    // this only runs when number == "3" is true
    print("correct!")
else
    // this only runs when number == "3" is false
    print("incorrect!")
endif
These can be nested like so:
number = input("guess my favourite number")
if number == "3" then
    print("correct!")
else
    if number == "4" then
        print("4 is my second favourite")
    endif
endif
However, the pattern of using another if immediately after an else clause is so common that there is a special keyword for it:
number = input("guess my favourite number")
if number == "3" then
    print("correct!")
elseif number == "4" then
    print("4 is my second favourite")
elseif number == "5" then
    print("5 is my third favourite")
else
    print("incorrect!")
endif
The elseif keyword is equivalent the the earlier code that used the keywords in sequence. Note that it is one word - there is no space between the "else" and "if".

In summary, if statements should have the following structure:
  • Always starts with an if block (with a condition)
  • Optionally has zero or more elseif blocks after (with a condition)
  • Optionally has an else clause at the end (without a condition)

switch statements

A switch statement can be used to match from a list of values. It can be viewed as a short hand for an if statement:
fruit = input("what is your favourite fruit?")
switch fruit:
    case "apple":
        print("apples are red")
    case "banana":
        print("bananas are yellow")
    case "blueberry":
        print("blueberries are blue")
    default:
        print("I don't know what colour that is!")
endswitch
The above code is exactly equivalent to the code below:
fruit = input("what is your favourite fruit?")
if fruit == "apple" then
    print("apples are red")
elseif fruit == "banana" then
    print("bananas are yellow")
elseif fruit == "blueberry" then
    print("blueberries are blue")
else
    print("I don't know what colour that is!")
endif
The default branch is similar to the else block of an if statement in that it is called if nothing else matches. The default branch is optional - if no match is found, the code is not run. It is also worth noting that the order is important here: if two branches have the same match condition, the one that is written first will always match instead of the latter ones.

It is often easier and more flexible to use if statements instead of switch statements. However, it is worth learning the syntax just in case it is needed.

In summary, switch statements should have the following structure:
  • Always starts with a switch statement containing the expression to compare to. This must end in a :.
  • Optionally has zero or more case statements containing the required value for the code below to run. These behave similarly to an elseif.
  • Optionally ends with a default branch in case no matches are made. This is similar to an else.

Iteration

There are two types of loops in OCR reference language: count-controlled and condition-controlled.
In the interpreter, loops run with a small delay between each iteration by default. This means that if an infinite loop occurs, the window will not freeze. If you need the performance, you can disable this in settings.

Count controlled

A for loop can be used to run code a set number of times. The bounds on a for loop are inclusive, meaning that both the start and end number are included in the range. This can be seen below
// prints hello 3 times
for i = 1 to 3
    // this runs three times, with i = 1, then 2, then 3
    print("hello")
next i
The value of i changes on each iteration as shown below:
// prints the numbers 1 through 10
for i = 1 to 10
    print(i)
next i
You can also specify a step with these loops:
// prints 0, 2, 4, 6, 8, 10
for i = 0 to 10 step 2
    print(i)
next i
// prints 5, 4, 3, 2, 1, Blastoff!
for i = 5 to 0 step -1
    print(i)
next i
print("Blastoff!")
Changing the value of i here will raise an error, and for good reason, as this is almost never what you want to do and is a recipe for disaster. If you are absolutely sure you know what you are doing, you can disable this in the settings at your own risk.

Condition controlled

There are two loops that can be used to iterate over a section of code an indefinite number of times: while and do ... until. Below is an example of a basic password input in both:
x = ""
while x != "parrot"
    x = input("password: ")
    print("you guessed", x)
endwhile
print("correct!")
do
    x = input("password: ")
    print("you guessed", x)
until x == "parrot"
print("correct!")
These two blocks of code do exactly the same thing - request input until the user enters "parrot". There are some subtle differences about how these loops behave:
  • The do ... until loop condition is inverted compared to the while condition.
  • A do ... until loop will ALWAYS run at least once, even if the condition is true. A while loop may never run at all if the condition is false.
  • The lifetime of x:
    • In the while loop version, x is in scope throughout the whole program.
    • In the do ... until loop version, x is in scope for the inner portion of the loop and the condition only.
This lifetime difference is illustrated below:
// this will NOT run - x is undeclared on the first line
while x
    x = true
endwhile
do
    x = true
until x
// this will throw an error - x went out of scope after the until line
print(x)
In the above example, if you need the value of x, you can declare it above the do loop:
x = 0 // this value does not matter, it is overwritten immediately
do
    x = true
until x
// this is ok now - x was declared in an outer scope and lives for the duration of the program
print(x)

Arrays

An array is an ordered collection of items that have the same datatype. The official syntax for defining an array is as follows:
array vowels[5] // define array vowels with 5 elements
The number inbetween the square brackets in the declaration denotes how many elements the array has.
To retrieve an item from an array, use the index operator arr[n]. This will give the nth element of the array arr. Arrays are zero-indexed, meaning that arr[0] contains the first element, arr[1] the second and so on. The same syntax can be used for assignment:
array vowels[5]
vowels[0] = 'a'
vowels[1] = 'e'
vowels[2] = 'i'
vowels[3] = 'o'
vowels[4] = 'u'

print(vowels) // ['a', 'e', 'i', 'o', 'u']

print(vowels[3]) // 'o'
Note that you do not have to index an array with a constant - you can use variables instead:
x = random(1, 5)
print(vowels[x]) // outputs some mystery letter from the array
When declared like this, all elements are initialised to be the special value <empty>. Trying to take this value out of an array will raise an error.
array vowels[5]
nothing = vowels[0] // error occurs here
print(nothing) // not here

// cannot take <empty> out of an array
Using a count controlled loop, we can iterate over all of the items in an array:
for i = 0 to vowels.length - 1
    print(i+1, "->", vowels[i])
next i
// prints:
// 1 -> a
// 2 -> e
// 3 -> i
// 4 -> o
// 5 -> u
You will notice that arr.length represents the number of elements the array was declared with.
The above pattern is very common for array iteration. Note that there is a - 1 on the end of the loop range - this is because for loops are inclusive (as mentioned earlier). Because arrays are zero indexed, arr[arr.length] is always invalid, as it is trying to index an item that is one element past the end of the array.

As mentioned earlier, arrays elements must all be of the same datatype (unless disabled in settings at your own risk). This means that members that are being inserted must adhere to the type currently stored in the array. If the array is empty, any value may be inserted.
array vowels[5]

vowels[0] = 'a' // ok, vowels currently has unknown type
// vowels is now an array of string

vowels[1] = 3 // not ok, throws an error
// cannot insert an integer into array of string

vowels[0] = 3 // still not ok even though all other elements are empty
Arrays are reference types. What this means is that when you assign one to a variable, they both reference the same data:
array vowels[5]
vowels[0] = 'a'
vowels[1] = 'e'
vowels[2] = 'i'
vowels[3] = 'o'
vowels[4] = 'u'

my_vowels = vowels // does NOT copy - my_vowels references vowels
my_vowels[2] = 'y' // replace 'i' with 'y' in my_vowels

print(my_vowels) // ['a', 'e', 'y', 'o', 'u'] as expected
print(vowels) // ['a', 'e', 'y', 'o', 'u'] also
This is because my_vowels references the same data as vowels, so changing either array will also affect the other. To avoid this, you can copy the arrays manually by using a for loop.
You may want to read the related topic Subroutine Referencing in subroutines.

2D arrays

2D arrays can be thought of as a 2D grid of objects. The way this is represented is by having an array of arrays.
To declare a 2D array, use the following syntax:
// array of 3 people's first names and surnames
array names[3, 2]
This declares an array with length 3 by 2. This means that the valid integers for the first index are 0, 1, 2, and 0, 1 for the second.
To read/write items to a 2D array, use the normal array index syntax, separating each index with a comma.
names[0, 0] = "bill" // row 1, column 1
names[0, 1] = "nye" // row 1, column 2

names[1, 0] = "jane"
names[1, 1] = "doe"

names[2, 0] = "james"
names[2, 1] = "bond"
These can be iterated over like normal arrays - they just need an extra loop:
for i = 0 to names.length - 1 // names.length = 3
    for j = 0 to names[i].length - 1 // names[i].length = 2
        print(names[i, j])
    next j
next i
This works because internally, they are just represented as an array of arrays, meaning that names[i][j] is perfectly valid and means exactly the same as names[i, j]

Alternate syntax

An alternate syntax for defining arrays is below:
vowels = ['a', 'e', 'i', 'o', 'u']
print(vowels) // 'o'
While this is certainly easier to write, it is not technically compliant ERL, so prefer the array keyword syntax in an exam.

You can also modify the settings of the interpreter to allow the use of different types in an array - you will almost never need to do this though.

Constants and arrays

As it stands, the interpreter currently forbids the syntax const array ... (although this may change in the future). However, there are still ways around this:
// use alternate syntax as above
const arr = [1, 2, 3]

// create a const binding to an existing array
array arr_b[3]
arr_b[0] = 1
arr_b[1] = 2
arr_b[2] = 3
const arr_const = arr_b
However, it is important to note that the const modifier only applies to the assignment/binding, and not the actual elements. This means that the following is perfectly valid:
const arr_const = ...
arr_const[0] = 2 // ok
arr_const[2] = 3 // also ok
In this case, all the const modifier actually does is restrict reassignment to the declared variable:
const arr_const = ...
arr_const = [2, 3, 4] // not valid, throws an error

Subroutines

A subroutine is a reusable section of code. Think of it like a list of instructions that can be run with optional input that gives a single (or no) value as output. They can be split into two categories: functions and procedures.

Functions

Functions must always give a single return value, and can be defined as below:
function add(a, b)
    return a + b
endfunction
And called (run) like this:
x = add(2, 3)
print(x) // 5
These calls can also be used inside other expressions, or just out in the open.
print(add(2, 3) * 5) // 25
print(add(5, add(3, 4))) // 12
add(4, 5) // no output - function is called, but return is not used
An example of a builtin function within the language is input or random.

Procedures

Procedures are similar to functions, but return no value. These are usually useful for modifying objects or printing data. For example:
procedure greet(name)
    print("Hello", name)
endprocedure

greet("Bill Nye") // prints Hello Bill Nye
greet("James Bond") // prints Hello James Bond
Or more complexly:
procedure fill(arr)
    for i = 0 to arr.length - 1
        arr[i] = i + 1
    next i
endprocedure

array x[5]
fill(x)
print(x) // [1, 2, 3, 4, 5]
The value of x is modified in place here. Procedures cannot be used inside expressions, unlike functions:
procedure greet()
    print("hello")
endprocedure

greet() // ok, prints hello
y = greet() // NOT ok - x does not return a value, so we have nothing to assign to y!
print(greet()) // also NOT ok - we have nothing to print!
Examples of procedures include print or newFile.

Subroutine referencing

Looking at the code above for fill, we can see that it modifies the array it was given. So logically, you might assume that we can change the value of any argmuent passed (e.g. an integer). However, as you can see below, this is not the case:
procedure add_one(a)
    a = a + 1
    print(a) // prints 3
endprocedure

x = 2
add_one(x)
print(x) // prints 2 ???
So why is this happening? Basically, when we assign to a, we change the value of, well, a. At the start of the function, it does in fact reference the variable x, but we cannot modify it - we can only change what it references (or the value "in its box"). Therefore, reassigning to a results in its value changing, but the value of x staying the same.

Arrays are no different - in the earlier examples, we modified the array elements, rather than changing the actual reference stored in a. This can be seen more clearly below.
procedure empty(a) // at the start, local variable a is equal to x and they both reference the same data.
    a = [] // local variable a is reassigned to be empty, x remains unchanged and the only reference to its contents
endprocedure

x = [1, 2, 3]
empty(x)
print(x) // prints [1, 2, 3], and we now know why :)