This feels like a really basic question, but I haven't been able to find an answer to it. How should a proof assistant implement separate compilation? Maybe the answer is "just like any interpreted programming language with a bytecode precompiler", but I don't know where to find the answer to that either. I found a lot of tutorials on the web for how to write an interpreter, but as far as I could tell, all of them stopped short of defining a way to load other source files; and tutorials on writing machine-code compilers all seem to focus on placement of code at physical addresses which doesn't seem especially relevant.
Specifically, what I want to know is how to deal with symbol resolution between compilation units. During ordinary typechecking, I am giving each defined constant a unique identity (currently an autonumber integer, like a global De Bruijn level), and then occurrences of that constant in other terms are resolved to reference that identity. That way a definition remains unchanged even if the particular names that are in scope to refer to the other constants it uses change, such as by import
or leaving a section
.
This is fine during a single run of the typechecker, as the autonumbers can be continually incremented no matter how many files we check. But if I want to store the typechecked terms in a "bytecode" file so that they can be loaded into another file without being re-typechecked, then the stored autonumbers will be those from the compilation run, which may conflict with those from the later run that's trying to load it. I could of course make up a solution, but this is clearly an already-solved problem and I just need a pointer to the best-practice approach.