These are the stages of compilation:
The first compilation stage reads the input from a file, from a string, or from the interactive command interpreter. The result is one or more Scheme forms (S-expressions), usually lists. If reading commands interactively, only a single form is read; if reading from a file or string, all the forms are read until end-of-file or end-of-string; in either case, the result is treated as the body of a dummy function (i.e. a ModuleBody).
The source form is rewritten into an Expression object, specifically a ModuleExp. This stage handles macro expansion and lexical name binding. It figures out which local variables are “captured” by an inner function, and hence need to be heap-allocated. (Other variables are stack-allocated in the Java local variable frame.) Various optimizations are done, including selection of closure representations.
The resulting ModuleExp is compiled into one or more byte-coded classes. This is done by invoking the virtual compile method recursively on the Expressions, which generates instructions (using the bytecode package) to evaluate the expression and leave the result on the Java operand stack. At the end we ask the bytecode package to write out the resulting classes and methods. They can be written to a file (for future use), or into byte arrays in memory.
The compiled bytecodes are loaded into the Kawa run-time. In the case of code that is compiled and then immediately executed, the compiled code can be immediately turned into Java classes using the Java ClassLoader feature. (That is how the read-eval-print loop works.) An instance of the compiled sub-class of ModuleBody is created and run, which normally produces various side-effects.