Languages and translators

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

F6.5Robust programsGCSE20 min

Covers OCR J277 2.5.1 Languages · AQA 8525 3.4.4 Classification of programming languages and translators · Edexcel 1CP2 3.3.1 Low-level and high-level languages · Edexcel 1CP2 3.3.2 Interpreters and compilers

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 device drivers, in parts of operating systems, and in small pieces of firmware, such as the code that starts a processor when it powers on.

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.

The robot assembly square: JNZ 1 jumps back to instruction 1 while R is not zero. R goes 3, 2, 1, 0, so the sides are driven four times0LOAD 41FWD 202TURN 903BEEP 6604DEC5JNZ 16HALTR not zero:jump back to 1R is zero: carry oninstructioneach time JNZ is reachedtime roundRJNZ13jumps to 122jumps to 131jumps to 140carries onFWD 20 runs 4 times: 4 sides
The green instructions are the loop. DEC takes R from 4 down to 0, one each time round, and JNZ jumps back until R reaches zero. There is no for or while: the loop is only a counter and a jump.

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.
A compiler translates the whole program before any of it runs; an interpreter translates and runs one statement at a timecompilerprogramline 1line 2line 3compilertranslates all3 lines firstmachine codefileruns on its ownfast, as often as you like,no compiler neededA mistake anywhere: every error is listed once translating ends, and nothing runs.interpreterprogramline 1line 2line 3interpreter, needed every time the program runsline 1:translate, runline 2:translate, runline 3:translate: error, stopA mistake in line 3: lines 1 and 2 have already run when it stops there.
The same three-line program both ways. The compiler finishes all its translating first and leaves a file that runs without it. The interpreter translates each line just before running it, so it is needed every time, and it only meets an error when it reaches that line.
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. RESUME at the top and RETURN_CONST at the bottom start and finish every compiled block; between them are the real work: load 20, load boost, load 2, multiply, add, store in speed. One line of Python became six low-level steps.

In these lessons Python is compiled to bytecode and interpreted by CPython. On the robot pocketpy interprets your program, while the motor and camera code is compiled Cin these lessonsyour Python programcompiled tobytecodesimple steps: load, add, storeinterpreted byCPython interpretercompiled to run in your browseron the real robotyour Pythonrun bypocketpyan interpretermotor andcamera code, Ccompiledmachine codefastcallsunderneath it all, a processor that only runs machine code
Both, and more. Your Python is always interpreted, while the code that has to be fast, such as the robot's motor and camera code, is compiled ahead of time. However many layers there are, what finally runs is machine code.

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.