Programming Languages & IDEs
Programming languages are grouped into two levels: high-level and low-level. The level depends on how close the language is to people or to the processor. A language close to English and maths is high-level; a language close to the instructions a processor carries out is low-level.
A high-level language uses readable words and familiar maths, so you can look at a line and roughly follow what it does. A low-level language uses instructions that match the hardware, so it is much harder to read. Machine code is the form the processor executes directly.
The word low-level does not mean poor, simple or old, and high-level does not mean better. These words only describe distance from the processor. A low-level language is nearer the hardware, which is exactly what a programmer sometimes wants.
In a high-level language, one statement can do a lot of work. Adding up every score in a list can be one short statement, and you never think about where those values sit in memory.
In a low-level language, the same job is broken into small steps: fetch one value, fetch the next, combine them, store the result. In machine code, each step is one instruction the processor can carry out directly. That is why low-level code is longer and harder to follow, but gives exact control over what the machine does.
Machine code is closest to the processor. Assembly language sits just above it, and high-level languages are furthest away, at the human end.
| Question to ask | High-level | Low-level |
|---|---|---|
| Closer to what? | People: English words and maths | The processor: its own instructions |
| How much work is one line? | Often many machine instructions | Usually one machine instruction |
| How easy to read and write? | Easier | Harder, needs knowledge of the hardware |
To classify a language or a statement about one, ask only one thing: is this aimed at people, or at the processor?
Machine code and assembly language are the two low-level languages. Both work at the level of the processor itself, so every instruction in them matches something the hardware can really do.
Machine code is written as binary patterns — strings of 0s and 1s. Each pattern stands for exactly one instruction in that processor's instruction set, which is the fixed list of operations the processor was built to carry out. One pattern, for example, might mean “load the number stored in memory location 6”. Machine code is the only form a processor can execute directly, with no translation at all.
Reading binary patterns is slow and easy to get wrong, so assembly language replaces each pattern with a short word called a mnemonic. The instruction above could be written as LOAD 6. There is one mnemonic for every machine code instruction, so the two languages have a one-to-one correspondence: a single line of assembly becomes a single machine code instruction, never several.
A processor cannot run assembly language, because it only understands binary patterns. Every assembly program therefore needs converting into machine code first, and that conversion is a straight swap of each mnemonic for its pattern. Assembly is therefore just a more human-readable way of writing the same instructions, not a different set of instructions.
Instruction sets differ from one processor design to the next, so a program written in a low-level language works on only one kind of processor. Move it to a different processor and it will not run, because the same patterns mean different things there, or nothing at all — the program has to be rewritten for the new processor. High-level languages are not tied to one processor like this, which is why they are portable.
Low-level languages – assembly language and machine code – work at the level of the instructions the processor actually carries out. That closeness brings real advantages, but it asks a great deal of the programmer.
For example, the routine that redraws a game screen sixty times a second might be written at a low level, so that each frame is finished before the next one is due. A low-level program can also be trimmed byte by byte to squeeze into a tiny memory limit, where a translated high-level version would not fit.
That is why low-level languages are used only when their benefits are essential: where memory is very limited, or where part of a program must run as fast as possible. For most other work, the ease of a high-level language is worth more than the control you give up.
High-level languages are programming languages written to be close to English and to maths. You write them as statements built from readable keywords such as for, def and return, so the code looks like a set of instructions a person can follow.
Each statement does a lot of work. When it is translated, one statement usually turns into many machine code instructions. For example, one statement that works out the average of the values in a list has to load each value in turn, add it to a running total, count how many values there are, divide the total by the count and store the answer — many machine code instructions from the single line you wrote.
Despite these drawbacks, high-level languages are used for most general-purpose programming because they are much easier to work with.
A translator is a program that converts code written by a programmer into machine code, so that it can be executed. Without one, the instructions a programmer types would just sit in a file and never run.
A processor can only execute machine code — the binary patterns that make up its own instruction set. It cannot act on a readable statement such as score = score + 5, because that is not one of its instructions. So every program written in a high-level language, and every one written in assembly language, has to be turned into machine code before it can run — and that is the translator's job.
Think of two people who speak different languages: the programmer writes in something a person can read, the processor only understands machine code, and the translator sits in between turning one into the other.
| Code the programmer writes | Can the processor execute it directly? | Does it need translating? |
|---|---|---|
| Machine code | Yes | No — it is already in the right form |
| Assembly language | No | Yes |
| High-level language | No | Yes |
The code written by the programmer is called the source code. It is the version a person can read, edit and store. The translator takes that source code as its input and turns it into machine code the processor can carry out. The two are versions of the same program: one written for a person to read and change, and one written for the processor to carry out.
For high-level languages there are two types of translator: the compiler and the interpreter. Both turn high-level source code into machine code the processor can carry out, but they go about the job in different ways.
A compiler is a type of translator. It takes the complete source code and converts it all to machine code before the program is executed. The result is an executable file. Once it has been compiled, the executable can be started as many times as you like; it does not need to be translated again, and neither the source code nor the compiler has to be there.
If you compile a quiz game on one Windows laptop, you can copy the executable to another Windows laptop and play it there without installing the compiler or handing over your code.
An interpreter is a translator that converts source code into machine code one line or statement at a time. Each line is executed immediately, then the interpreter moves on to the next one. It does not create an executable file, so the program is translated again every time it is run.
If an error occurs in a line, either while the line is being translated or while it runs, the interpreter stops at that line and displays an error message. You can see exactly where the problem is, which helps you find and fix it. Earlier lines have already been executed, so the program may have produced some output before it stops.
total = 8 + 4
print(total)
average = total / 0
print(average)Output:
12
ZeroDivisionError: division by zero (line 3)
Interpreters are most useful during development; compilers suit finished programs that are given to users.
Choosing between a compiler and an interpreter depends on two things: how far through development the program is, and who will end up running it. Both turn high-level code into machine code, so the choice is about what suits the job, not about what they do.
A compiler translates the whole program in one go and produces an executable file. That makes it the sensible choice when a finished program will be installed and run by lots of people, because:
An interpreter translates and runs the code one line at a time, so it halts as soon as a line goes wrong and tells you which line the problem is on. You fix that line and run again straight away, with no compiling step in the way, and you can run a program that is only half finished.
For example, a school installs a finished revision quiz on the computers in its study centre. A compiler suits this: the quiz is translated once, the executable is copied onto every machine, and nobody can open it up and rewrite the questions.
| Situation | Choose | Why |
|---|---|---|
| A finished program being sold or given to many users | Compiler | The executable runs quickly, users need no translator and the source code stays private |
| Testing a section of code you have just written | Interpreter | It stops on the faulty line and a fix can be tried out at once |
| Users must not be able to alter the code | Compiler | Users get only the executable, so the source code is never handed over |
So ask who is running the program: someone still fixing it wants an interpreter, while someone who just needs it to work wants a compiler.