A report that follows every rule
A stock report script reads: print total + step. The grammar is happy. Every token is fine.
But step only existed inside a loop that has already ended. A parser checks the shape of a sentence, not whether it makes sense. The next part of the compiler checks the meaning.
By the end of this lesson
Note
I can find the mistakes a parser cannot.
Key words
| Word | What it means | Example |
|---|---|---|
| Semantic analysis | checking that a program makes sense | |
| Declaration | the line that brings a name into being | int step |
| Scope | the part of a program where a name exists | |
| Symbol table | the compiler's list of names in scope | |
| Type | what kind of value something holds | int |
| Warning | a message that does not stop the build |
The cells students fill in are left blank in this preview.
The compiler keeps a list of names
As it walks the syntax tree, the compiler keeps a symbol table. Each { opens a new scope on top of the table. Each declaration adds a name to the top scope. Each } throws the top scope away.
To check a name, the compiler looks in the top scope, then the one below, and so on. A name found nowhere is undeclared. The Runs show where step lives in scope.c.
Go into the compiler folder
cd ~/compilerShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Every line that names step
grep -n "step" scope.cShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Worked example: the symbol table of scope.c
| Line | What happens | Names in scope |
|---|---|---|
| 4 and 5 | main declares unused and total | unused, total |
| 6 | The loop opens a scope and declares i | unused, total | i |
| 7 | The loop body declares step | unused, total | i | step |
| 9 | The loop ends: its scopes are thrown away | unused, total |
| 10 | printf uses step: not found | unused, total |
The cells students fill in are left blank in this preview.
Compile with no extra checks
gcc -c scope.cShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Now ask for the common warnings
gcc -Wall -c scope.cShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Every value has a type
The compiler also works out the type of every expression and checks it fits where it is used. square takes an int. The text "7" is not an int: in C it is the address of some characters, a char *.
The first Run makes types.c, a copy of hello.c with square("7"). The original is untouched. The second compiles it.
Make a copy with a wrong type
sed 's/square(7)/square("7")/' hello.c > types.cShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Compile the copy
gcc -Wall -c types.cShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Errors stop the build, warnings do not
The next copy, fmt.c, asks printf for two numbers and gives it one. gcc knows how printf reads its format, so it can count.
This time it gives a warning. The build goes on, and ls shows that fmt.o was written. After an error, no object file is written at all.
Make a copy with a missing number
sed 's/is %d/is %d %d/' hello.c > fmt.cShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Compile it
gcc -Wall -c fmt.cShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Was an object file written?
ls fmt.oShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A common mistake
Warning
Many people think a warning can be ignored because the program still builds. A warning is the compiler saying it found something that is legal but very likely wrong. fmt.c builds, then prints a number nobody passed. Read every warning, and treat it as a bug until you know why it is safe.
Python checks while the program runs
C checks names and types before the program runs: that is static checking. Python checks them only when the line runs, so the mistake shows up as an error at that moment.
Both Runs below start the program. Neither mistake is found until Python reaches it.
A name Python has never seen
python3 -c "print(total)"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Two types that do not add
python3 -c "print(1 + 'a')"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Going further
Extend your checker with a second list, unused. It reports every name that was declared and never used, with the line of its declaration.
Check it on PROGRAM with one extra line at the top, let spare = 1. Then say why gcc reports unused only when asked with -Wall, but always reports step.