Nearly all code generation routines receive a parameter that is an instance of type CodeContext. A CodeContext provides three distinct services to assist with code generation. Understanding these three services is critical to understanding code generation. Statement Accumulation ====================== First and foremost, a CodeContext collects C statements. In this regard, a CodeContext may be treated exactly as an ostream: context << "foo(y);\n"; Note that a CodeContext provides all of the formatting functionality of a standard ostream. You are not limited to just printing strings: context << "x = " << sqrt( 2 ) << ';' << endl; Lines will appear in the generated C source file in precisely the order in which they were written to the CodeContext. The CodeContext itself does not modify or filter the C statements; it simply collects it and streams it out to the C source file. Anything that ultimately represents valid C program text is fair game. Note, however, that freely writing out variable declarations is *not* fair game. C requires that all variable declarations precede all statements within a block. If you write out a variable declaration in the middle of other code, the resultant C source file will not compile. Variable Declaration Accumulation ================================= Placing all variable declarations before any statements can be tricky. The second service provided by CodeContext makes it easy. When a code generation routine determines that it will need to declare a C variable, it should not write the declaration out using the "<<" stream operator. Instead, it should use the CodeContext method provided for just that purpose: void CodeContext::declare( const string &name, const string &type ); For example, if we wish to declare an integer "i" and set it to 5, we might write: context.declare( "i", "int" ); context << "i = 5;\n"; A CodeContext collects variables and statements in parallel. When the time comes to write out a C source file, each CodeContext will guarantee that all of its variable declarations appear before any statements. Thus, even if code was generated with statements and variables intermixed... context.declare( "i", "int" ); context << "i = 5;\n"; context.declare( "d", "double" ); context << "d = 2.75;\n"; ...the generated C source file will keep everything in the proper order: int i; double d; i = 5; d = 2.75; The important thing to remember is that you should never write variable declarations out using "<<". Use the CodeContext::declare() method instead. Block-Based Code Streaming ========================== The third service provided by CodeContexts is to keep track of the way in which subblocks are nested within outer blocks. Each CodeContext ultimately represents a set of variable declarations followed by a set of statements. Thus, each CodeContext really represents the eventual contents of a single C block. Indeed, when a CodeContext writes itself out, it places its entire contents within curly braces, just like a C block: context << "assert( y > 0 );\n" << "x = y;\n"; context.declare( "x", "int" ); || || _||_ \ / \/ { int x; assert( y > 0 ); x = y; } So far, though, we have not described *where* the C block actually appears. Does it go to standard out? Into some file? Into memory? The answer is any of the above. When you construct a CodeContext, you provide an ostream that will be the ultimate recipient of the C block that the CodeContext is assembling. For example: ofstream sink( "generated.C" ); CodeContext context( sink ); . . . Any variables or statements collected by the above CodeContext will ultimately appear as a C block written into the file "generated.C". Now, C blocks can nest. We may want one CodeContext to place a block inside another CodeContext's block. This can be accomplished easily. Recall from above that a CodeContext can be treated just like any other ostream. Thus, a CodeContext can be the sink for another CodeContext: ofstream sink( "generated.C" ); CodeContext context( sink ); CodeContext subcontext( context ); When "subcontext" generates its code, it will place a self-contained C subblock into "context", as though that entire subblock were a single C statement. Eventually, "context" will emit its own block into the named file. That block will including the subblock that it picked up from "subcontext". Now we know "where". How about "when"? At what point does a CodeContext consider its accumulation complete? We don't want to emit the block too soon, since there might more variables or statements coming later. Therefore, CodeContexts wait until the last possible moment: the accumulated block is streamed out in the CodeContext's destructor. This means that the timing of block and subblock generation depends upon the lifetimes of CodeContexts. Suppose we ultimately wanted to generate the following C code on standard output: { foo(); { bar(); baz(); } zing(); } The corresponding CodeContext deployment would be: { CodeContext outer( cout ); outer << "foo();\n"; { CodeContext inner( outer ); inner << "bar();"; inner << "baz();"; } outer << "zing();"; } The use of scope to restrict the lifetime of the inner block is critical. When "inner" goes out of scope, its destructor streams out its accumulated contents as a single C block, complete with curly braces. This subblock is received by its sink, "outer", which just buffers the subblock right after "foo()". When "outer" finally goes out of scope, it dumps its contents into its sink, which is just standard output. Then the entire outer block, including its inner subblock, appears as desired. The timing and nesting of contexts and subcontexts sounds complex, but it generally just works behind the scenes without requiring too much attention. When you want a new C block inside the current one, declare a new context with the current context as its sink. The important point is that the lifetime of a subcontext should reflect the desired sequential placement of its code block within its sink. - * - And that, dear reader, is the tale of code generation contexts.