This program gives different results in the interpreter vs what you'd expect. Why?
var x: i64 = 10;
fn foo() i64 {
var x: i64 = 99;
return x;
}
foo()
It returns 99, not 10. Inside foo(), var x: i64 = 99; creates a local variable named x (because we're inside a function). return x; finds the local x first (locals are checked before globals). The global x is shadowed but not modified.
This is correct behavior -- local variables shadow globals with the same name. It's how most languages work. But it's a trap if you accidentally reuse a name. Our language doesn't warn about shadowing. Something to keep in mind when writing the self-hosted compiler: don't name a local the same as a global.
Globals are the single biggest feature needed for self-hosting. Every function in the self-hosted compiler -- cur(), skip(), number(), read_name(), emit_byte(), emit_s(), emit_num(), every c_* function -- reads or writes global state. Without globals, none of them can communicate. With globals, they all share the same src_pos, out_pos, and src_len, just like the Zig versions share Zig globals.
The pattern is now proven: declare globals at the top level, access them from any function, mutations are visible everywhere. The interpreter, compiler, and VM all agree. We can move on.
## Part 10: Completing the Language
Our language is a subset of Zig. Every program we write should compile with both our compiler and the Zig compiler. This chapter adds the remaining operators and features to close the gap.
### Comparison operators