Languages and translators

High and low level, machine code and assembly; compilers, interpreters and assemblers.

F6.5Robust programsGCSE20 min

Do this lesson in the simulator

BugBot's processor cannot read Python. Nor can the one in your laptop. A processor understands only its own machine code: patterns of binary that mean things like "add these two numbers" or "jump to that instruction". Every program in every other language has to be translated into machine code before it can run. This lesson is about the kinds of language, and the programs that translate between them.

High level and low level

High-level languages Low-level languages
Examples Python, Java, C#, Scratch machine code, assembly language
Looks like English words and maths: if distance() < 30: binary, or short codes: LOAD 4, JNZ 1
One statement does many processor instructions one processor instruction
Runs on any processor, once translated one family of processors
Good for writing programs quickly and readably total control of speed, memory and hardware

Most programs are written in high-level languages, because they are faster to write, easier to read and to fix, and not tied to one kind of processor. Low-level code is used where every byte and microsecond counts: in the firmware that drives BugBot's motors, in device drivers, and in parts of operating systems.

Assembly language

Machine code is unreadable binary, so each instruction is given a short name instead, called a mnemonic. That is assembly language: one line for each processor instruction. Here is a made-up assembly language for the robot, with a register, a single memory slot called R:

Instruction Meaning
LOAD n put the number n in R
FWD n drive forward n cm
TURN n turn n degrees
BEEP n play a note of n hertz
DEC take 1 away from R
JNZ a if R is not zero, jump to instruction number a
HALT stop

There is no for and no while. A loop is made with a counter and a jump back:

0  LOAD 4
1  FWD 20
2  TURN 90
3  BEEP 660
4  DEC
5  JNZ 1
6  HALT

This drives a square: R starts at 4, and each time round DEC takes one off and JNZ 1 jumps back to instruction 1, until R reaches zero.

Translators

A translator turns a program into machine code. There are three kinds:

  • An assembler translates assembly language into machine code, one instruction for one instruction.
  • A compiler translates a whole high-level program into machine code in one go, before it runs. The result is a file that runs on its own, fast, without the compiler. Errors are all reported at the end of compiling.
  • An interpreter translates and runs a high-level program one statement at a time. There is no separate file: the interpreter is needed every time the program runs. It stops at the first error it meets, which makes it good for developing and testing.
Compiler Interpreter
Translates the whole program, before running one statement at a time, while running
Produces a standalone machine code file no file
Speed when running fast slower
Errors all reported after translation the program stops at the first one
Source code needed to run no yes, and the interpreter

Which does BugBot use?

Both, and more. The Python you write in these lessons runs in an interpreter: CPython, itself compiled to run inside your web browser. On the real robot, a small Python interpreter called pocketpy runs your program, while the motor and camera code underneath is written in C and compiled to machine code, because it has to be fast.

Python actually does a bit of both. Before running, it compiles your program into bytecode, simple instructions for its own virtual processor, then interprets the bytecode. You can see it:

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

import dis

dis.dis(compile("speed = 20 + boost * 2", "sum", "exec"))

Run this in the simulator

Each line is one bytecode instruction: load a value, multiply, add, store. One line of Python became several low-level steps.

An interpreter of your own

An interpreter is just a program that reads instructions and carries them out. Here is the start of one for the robot's assembly language:

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

program = ["FWD 20", "TURN 90", "BEEP 660", "FWD 20", "HALT"]

pc = 0                               # program counter: which instruction is next
while True:
    op, *args = program[pc].split()
    print(pc, op, args)
    if op == "FWD":
        forward(60, distance=int(args[0]))
    elif op == "TURN":
        turn_right(30, angle=int(args[0]))
    elif op == "BEEP":
        tone(int(args[0]), 0.2)
    elif op == "HALT":
        break
    pc = pc + 1

Run this in the simulator

The program counter pc holds the number of the next instruction, the same job a real processor's program counter does. Each time round, the interpreter fetches an instruction, works out what it means, and does it. That fetch, decode, execute loop is exactly what a processor does with machine code, which is module F8.

Task: robot assembly

Finish the interpreter so it runs the robot assembly program below, which uses LOAD, DEC and JNZ to drive a square with a beep at each corner. Store the value of R in a variable, and make JNZ change the program counter.

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

program = ["LOAD 4", "FWD 20", "TURN 90", "BEEP 660", "DEC", "JNZ 1", "HALT"]

pc = 0
while True:
    op, *args = program[pc].split()
    if op == "FWD":
        forward(60, distance=int(args[0]))
    elif op == "TURN":
        turn_right(30, angle=int(args[0]))
    elif op == "HALT":
        break
    pc = pc + 1

Challenges

  1. Add a WAIT n instruction, and write an assembly program that flashes a beep three times.
  2. Add JMP a, an unconditional jump. What happens if a program jumps back with no way to stop?
  3. Write your own compiler of sorts: a function that turns the assembly program into a Python program as text, and print the text.