Languages and translators
High and low level, machine code and assembly; compilers, interpreters and assemblers.
Do this lesson in the simulatorBugBot'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"))
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
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
- Add a
WAIT ninstruction, and write an assembly program that flashes a beep three times. - Add
JMP a, an unconditional jump. What happens if a program jumps back with no way to stop? - Write your own compiler of sorts: a function that turns the assembly program into a Python program as text, and print the text.