Automatic Header File Generation

1. Introduction

1.1. Purpose of the utility

This appendix describes a utility, for historical reasons called mdgen 
(module definition generator), that extracts header files from marked-up 
source files. 

The utility solves one problem: the separate maintenance of header and 
implementation files. In C++, function and variable declarations and class 
definitions must be contained in a header file, whereas the implementation 
of functions and operations must be placed in an implementation file, 
except of course for inline functions. Making a change means fixing two 
files. This bothers some programmers a lot; others say `So what?'. If 
maintaining two sets of files isn't a problem for you, then don't use 
mdgen. If it is, read on.

1.2. Running mdgen

To run mdgen, simply specify the source files you want to scan. 

    mdgen date.cpp str.cpp hw*.cpp

This produces (or updates) files date.h, str.h, and hw*.h for all files 
matching hw*.cpp.

Just try it out with one of our files, say date.cpp. 

1.3. Double scan protection

The header files produced by mdgen are automatically protected against 
double scanning. The entire file is surrounded by directives

    #ifndef DATE_H
    #define DATE_H

    // ...

    #endif

1.4. Error reporting

If you make a syntax error in an implementation file that causes a faulty 
header file to be extracted, the compiler will report the error in the 
source file, pointing to the actual offending line. The mdgen utility 
places #line directives in the header file for this purpose.

If the compiler complains about syntax errors, fix the errors in the source 
file and run mdgen again.

1.5. Interaction with `make' utilities

The mdgen utility respects time stamps of header files. If the changes in 
date.cpp do not require a modification in date.h, the old date.h is not 
changed. You can simply run

    mdgen *.cpp

to refresh all header files. You need not worry that the `make' or project 
file will trigger a complete recompile. If no change in a header file was 
detected, it is not touched. 

You can teach your `make' or project tool a dependency rule to invoke mdgen 
automatically. For example, Borland `make' accepts the following rule set:

    .cpp.obj:
     $(cc) -P $(copts) $<

    .asm.obj:
     $(asm) $<

    .cpp.h:
     mdgen $< 

2. Marking up the source file

2.1. The EXPORT tag

Unfortunately, mdgen is not smart enough to figure out what features to 
extract from the source file into the header file. You have to tag all 
exported features with the string EXPORT. In practice, this is not a great 
problem--certainly less trouble than switching back and forth between edit 
windows. 

EXPORT is defined in setup.h as the empty string and has no influence on 
the compilation. 

2.2. Include your own header file

For mdgen to work, it is essential that you include your own header file in 
the implementation file.

    #include <iostream.h>

    // ...

    #include "employee.h"

This is not surprising. You would have to do that anyway if you didn't use 
mdgen.

2.3. Global variables and functions

Let us start with the easiest case: tagging global variables and functions. 
Just prefix them with the tag EXPORT.

    EXPORT int days_per_month[12] = { 31, 28, ..., 31 };

    EXPORT istream& operator>>(istream& is, Date& date)
    {  // ...
    }

Then mdgen places the following lines into the header file:

    extern int days_per_month[12];

    extern istream& operator>>(istream& is, Date& date);

That is, the variable initializer and function body are ignored, and the 
declaration is terminated with a semicolon.

You only tag the variables and functions that you want to export. As a 
consistency check, see that all global variables and functions are either 
tagged as EXPORT or static. 

You benefit from the automatic extraction in two ways. You don't have to 
type the declaration twice. And if you make a change in the function 
definition, file, just run mdgen and let it update the header file for you. 

2.4. Classes

Defining classes in an implementation file creates an unfortunate problem. 
The implementation file reads in its own header file, but the compiler 
refuses to read the class definition twice. Our solution is not pretty but 
it works. Rather than tagging a class with EXPORT, surround it with #ifndef 
EXPORT ... #endif.

    #ifndef EXPORT

    class Date
    {
    public:
       // ...
    };

    class Time
    {
    public:
       // ...
    };

    #endif

Then mdgen copies everything inside this comment block to the header file. 

You may need to place a class declaration into the header file, either to 
deal with circular dependencies or to avoid reading an entire header file. 
Just tag the declaration.

    EXPORT class iostream;

    EXPORT class Time;

You tag only the class declarations, not the declarations of the 
operations. 

2.5. Other types

Unions, structures and enumerations are handled just like classes. Include 
them inside an #ifndef EXPORT ... #endif block.

Type declarations (typedef) can be tagged with EXPORT.

2.6. Inline functions

Inline functions must be copied to the header file because the inline 
replacements may need to be carried out in the compilation of other 
modules.

Place inline functions inside an #ifndef EXPORT ... #endif block.

    #ifndef EXPORT

    inline int Date::day() const 
    // RETURNS:   the day of this date
    { return _day; }

    inline int Date::month() const 
    // RETURNS:   the month of this date
    { return _month; }

    inline int Date::year() const 
    // RETURNS:   the year of this date
    { return _year; }

    #endif

2.7. Constants

The handling of constants in C++ is murky. By default, global constants are 
static, that is, not visible from other modules. This is to enable the 
compiler to perform inline replacement on constants rather than using 
memory lookups:

    const int DAYS_PER_YEAR = 365;

    y = d / DAYS_PER_YEAR; // compiles as d / 365

Integer constants can even be used at compile time!

    const int BUFFER_SIZE = 80;

    char buffer[BUFFER_SIZE];

This is different from C. In C, constants always occupy memory and they are 
never evaluated at compile time. 

Often, the value of such an `inline constant' needs to be visible to other 
modules during compilation, and the definition must be placed in the header 
file. 

In C++, a constant that occupies storage and can be referenced from other 
files must be declared and defined as extern const:

    extern const int days_per_month[12] = { 31, 28, ..., 31 };

    extern const Complex imag_unit = Complex(0, 1);

These are fairly rare. 

Both cases are handled by mdgen. Place inline constants inside an #ifndef 
EXPORT ... #endif block. The code

    #ifndef EXPORT

    const int DAYS_PER_YEAR = 365;
    // ...

    #endif

    EXPORT extern const int days_per_month[12] = { 31, 28, ..., 31 };

becomes

    const int DAYS_PER_YEAR = 365;

    extern const int days_per_month[12];

in the header file.

2.8. Templates

Templates of classes, operations, functions and variables must be copied to 
the header file. Place them inside an #ifndef EXPORT ... #endif block:

    #ifndef EXPORT

    template<class X> class Array
    /* PURPOSE:    smart array class template
       RECEIVES:   T - any type with copy construction and assignment
    */
    {
    public:
       // ...
    };

    template<class X>

    Array<X>::Array(const Array<X>& b)
    :  _low(b._low),
       _high(b._high)
    {  // ...
    }

    // ...

    #endif

2.9. Header files

To copy a #include directive, place a keyword EXPORT on the preceding line.

    EXPORT
    #include <iostream.h>

If the header file doesn't require the contents of iostream.h except to 
know that istream and ostream are classes, you can generate a more 
efficient header file by exporting only the class declarations

    // no EXPORT
    #include <iostream.h>

    EXPORT class istream;
    EXPORT class ostream;

2.10. Summary

Actually, the tagging process is simpler than it sounds. There are two 
steps:

    (1)    Tag the #include directives and declarations that you want in 
           the header file with EXPORT. 

    (2)    Enclose exported definitions (anything that the compiler won't 
           scan twice) inside #ifndef EXPORT ... #endif.  

3. Warts

Currently, mdgen is implemented as a compiled awk script. The script has 
evolved over several years and handles most cases smoothly. However, a few 
ugly problems remain. 

3.1. Default arguments

Default arguments must be part of the declaration of a function and not 
repeated in the definition. For operations of a class, this is not a 
concern because the operation declaration is inside the class and distinct 
from the definition. But for global functions, a separate declaration must 
be provided.

    EXPORT void display(Factory& f, Bool use_color = TRUE) // DON'T
    {  // ...
    }

Separate this into a declaration followed by a definition:

    EXPORT void display(Factory& f, Bool use_color = TRUE);

    void display(Factory& f, Bool use_color)
    {  // ...
    }

This is a kludge but fortunately rare. There is nothing mdgen could do 
better short of editing the source file, which the current version does not 
do.

3.2. Constructors of global objects

The mdgen program cannot tell the difference between certain variable 
definitions and functions. As a consequence, it will not remove the 
initializer. Consider

    EXPORT Complex imag_unit(0, 1); // DON'T

Even many human readers must look twice to see that it is not a function 
call. Instead, use

    EXPORT Complex imag_unit = Complex(0, 1); // OK
