In order to avoid circular compiler dependencies, neat.base
contains a
CompilerBase
class that is implemented in main
and passed through
to all compiler calls.
Normally, the compiler classes would be divided into cleanly separated modules. However, compilers are annoyingly interconnected. When parsing, most more involved parsed constructs will require looping back into statement/expression parsing. Similarly, AST and runtime classes are usually expressed in terms of other classes, which again introduces cycles. For example, the array feature requires generating an array-append function that loops over source elements, which pulls in functions, scopes, loops, etc.
To resolve this, the compiler defines CompilerBase
as a generic interface to the
rest of the compiler. This class allows parsing source, creating AST trees, or
creating IR trees. This works because usually when we're creating an IR or AST
object, we don't particularly care about it for its members or methods, but just use
it to represent a certain behavioral meaning in the program tree. So we don't care
that an astIndexAccess call creates an ASTIndexAccess class, so much as an ASTSymbol
class that happens to represent an index access.
In the context of macros called inside the compiler, the existence of CompilerBase
leads to a snag.
Since the macro will be pulling in the original compiler's version of CompilerBase
,
we cannot use new Compiler
features immediately. Instead, use the version statement
to make the macro work on both bootstrap and current versions of the compiler.