Prequel
primitive recursive driven programming


Prequel Primer

Each Prequel program is created to operate in a context given by the particular input variables and procedures available.

Begining programmers can start by calculating the factorial of a number or by sorting a list of numbers, or even by animating a line of 8 pixels.
As they get to know the language and the debugger, they can try to escape from a labirinth. Or try out an inductive programming game.
It is also possible to play a match with up to four PrequelBots in a cyclic arena.

With this in mind, this primer can help people who know other programming languages understand the differences to Prequel.

Prequel is a primitive recursive driven imperative language with two datatypes (lists and numbers) and two control structures (conditionals and bounded loops).
If you know other imperative languages, such as C or Python, there are only a few features worth noting.

Comments

Comments start with symbol '#' and continue until the end of the line.

  # This line is a comment.
  # Comments start with symbol '#'
  #  and continue until the end of the line.

Input Variables and Procedures

There are two types of input to a Prequel program: input variables and procedures.
Syntactically, they are both sequences of alphanumeric characters separated by dots (symbol '.').

In the factorial example, factorial.input is an input variable containing the argument to the factorial function.
After calculating the result, the procedure factorial.return should be called to terminate the program with the correct value.

It is important to note that input variables and procedures are read-only and thus cannot be changed directly by the programmer.

Variables and Assignment

Variables are sequences of alphanumeric characters (without dots) and can be created or updated with the assignment operator (symbol '=').

For example,

  n = factorial.input
assigns to variable n the contents of input variable factorial.input.

Numbers

All numbers in Prequel are signed 32-bit fixed point with 16 fraction bits.

Arithmetic Operators

Six arithmetic operators are available: addition (symbol '+'), subtraction (symbol '-'), multiplication (symbol '*'), division (symbol '/'), integer division (symbol '//'), and modulo (symbol '%').

Shorthands for these operations are also available to combine with assignments. For example,

  n++    # same as n += 1
  n += 1 # same as n = n + 1

Bitwise Operators

The following bitwise operators are available: and (symbol '&'), or (symbol '|'), not (symbol '~'), xor (symbol '^'), shift left (symbol '<<'), and shift right (symbol '>>').

Shorthands for these operations are also available to combine with assignments. For example,

  n~~    # same as n = ~n

Random Numbers

Prequel has built-in support for random numbers through the random operator (symbol '~=').

Given a positive integer n, we can obtain a random index, an integer between 0 and n-1.

  r ~= 10 # r gets a random number
          #   between 0 and 9

If a random number in the full range is needed, the keyword random can be used instead of the arithmetic expression.

  r ~= random # r gets a random number
              #   between -32768
              #       and +32767.99998474121

Lists

Structured data can be expressed through lists.
List elements can be numbers or lists themselves.

For example,

  xs = [1, 2, [3, 4], 5, []]

List access

Individual elements can be accessed through list subscripts.
For example,

  n = xs[2][0] # n gets value 3

List length

The length of a variable of type list can be obtained with the suffix .length.
For example,

  n = xs[2].length # [3, 4] has length 2

List search

The suffix .indexof can be used to search for elements in variables of type list.
If the element is found, it returns its index position in the list. Otherwise, it returns -1.
For example,

  n = xs.indexof(5) # 5 is found at index 3
Note that it does not continue the search inside nested lists:
  n = xs.indexof(4)    # not found (n gets value -1)
  n = xs[2].indexof(4) # 4 is found at index 1

List manipulation

It is possible to add an element to a variable of type list using a push instruction.
Similarly, it is possible to remove an element from a variable of type list using a pop instruction.
For example,

  xs.push(6, 5) # xs change to [1, 2, [3, 4], 5, [], 6]
  n = xs.pop()  # if the list index if omitted,
                #   it defaults to the end of the list

Note that this does not apply to input variables since those are read-only.

List copy

Assignment copies lists.
For example,

  ys = xs   # ys get a copy of xs
  ys[0] = 0 # xs remain unchanged

Conditionals

An if instruction can be followed by one or more elif, an optional else, and terminates with endif.
For example,

  if n is undefined
   n = 0
  elif n == 1 or n >= 3
   n = 1
  elif n <> 0
   n = 2
  else
   n = 3
  endif
Note that the blocks of instructions are made clear by their indentation.

Relational Operators

Relational operators can be used to check whether two expressions are: equal (symbol '=='), different (symbol '<>'), relatively less than (symbol '<'), less than or equal to (symbol '<='), greater than (symbol '>'), and greater than or equal to (symbol '>=').

Logical Operators

The three operators, and, or, and not, can be used in the logical expressions of instructions if and elif.

Bounded Iteration

A variable containing a number n can be used to repeat a block of instructions n times.
The variable is decremented each time the program counter enters the repeat block and the programmer cannot modify the variable while inside the repeat block.
For example, in

  total = 0
  n = 4
  repeat n
   total += n
  endrep
after repeating the sum four times, variable n is equal to zero and variable total is equal to six (3+2+1+0).

Early-Terminating Iteration

As in other programming languages, in Prequel we also have break and continue.

Infinite Loops

Prequel imposes a programming discipline that prohibits recursive calls and unbounded loops.
The runtime system derives its Turing completeness from re-entering a module whenever the execution slips past its last instruction.
You can imagine each Prequel module being contained inside a while true loop.

The Ackermann example was created to illustrate this feature of Prequel.

Modules

A Prequel program is a sequence of modules.
Program execution starts at the entry module.
Modules are similar to functions in other languages.
The entry module is the module named "main".
If there is no module named "main", the entry module is the topmost module.
To tame recursion in Prequel, modules cannot be called recursively.

Modules are called with procedure call and return execution with procedure return.
Call arguments are copied to the input list call.args.

  @entry_module:
  x, y, z = call(@another_module, 42)
  ...

  @another_module:
  n = call.args[0]
  return(n, n, n)

Callstack-variable operator

Variables in other modules can be accessed directly through the callstack-variable operator (symbol '!', syntax '!varname@modname').

  @yet_another_module:
  x_copy = !x@entry_module
  !x = x_copy + 1

The module name is optional. If omitted, it refers to the entry module.

More Documentation

Although there is more to be said about Prequel, hopefully this is enough to get you started.
Try the examples and have a look at their links to GitHub repositories for more Prequel code.
There are also slides providing some context and the theoretical underpinnings of Prequel.
Every valid source code in Prequel conforms to this grammar.

Happy Prequel programming!