Preprocess - A preprocessor for C and C++ modules

Do you hate writing C or C++ header files?

Do you always forget where to place those inline, virtual, static, and explicit modifiers -- to the member-function definition or declaration?

Do you often find yourself avoiding to factor out a new method because you also need to add a method declaration to the class declaration in the header file?

Do you hate putting inline function into header files, in a special order?

If so, Preprocess may be your answer. With this tool, you write unit-style single-source-file modules in C++, from which it automatically generates header and implementation files.


Table of contents:

Synopsis

preprocess -c filename_base [ -o outfile_base | -s]
           [ -p prepend ]
           [ -C source_ext ] [ -H header_ext ]
           [ -e tag_list ]
           [ -i ] [ -t ] [ -l | -L ] [ -v ] [ -d ]
           sources...

Description

Preprocess is a preprocessor for C++ modules. With this tool, you write unit-style single-source-file modules in C++.

Preprocess essentially does these things (and frees the programmer from doing them):

These automatisms lead to source files that are more logically cohesive (no need to put inline and template functions into a separate file, in a special order) and saves typing effort.

Preprocess works by transforming unit-style single-source-file C++ modules into three output files: public header, private header, and implementation file. C and C++ comments are preserved during the transformation, keeping the resulting files readable and making it possible to run another preprocessor on Preprocess' output.

Basic operation

Modules contain two sections. The "INTERFACE:" section contains the public interface to the module; the "IMPLEMENTATION:" section contains everything else. The preprocessor puts interface declarations into a public header file and tries to hide everything else.

Class definitions deliberately lack declarations for member functions: Preprocess will add them automatically. Member-function definitions (in the implementation section) labelled PUBLIC, PROTECTED and PRIVATE are put into the corresponding section of the class. This feature saves typing effort and reduces duplication.

From this input file (foo.cpp), Preprocess extracts three C++ source files that can then be processed using the standard C++ compiler toolchain:

// C++ module -- Preprocess input file
INTERFACE:

#include "bar.h"

struct Baz;

class Foo : public Bar
{
  Baz* d_baz;
};

IMPLEMENTATION:

struct Baz
{
  int data;
};

PUBLIC
int Foo::get_info() 
{
  return d_baz->data;
}

First, Preprocess generates a public header file (foo.h). This header file contains all declarations from the interface section, with class definitions properly expanded to contain member-function declarations (shown red in the example on the right side).

It also contains declarations of non-"static" free functions found in the implementation section, as well as definitions for functions declared "inline". Additionally, it contains all "#include" directives and function and class declarations and definitions the inline functions need; these dependencies need to be declared in the input file. (These features are not shown in the example on the right side.)

If desired, preprocess can also be instructed to hide all inline functions (and to generate them out-of-line instead), resulting in a public header file that better insulates clients from implementation details.

// Preprocess-generated public header file
#ifndef foo_h
#define foo_h

#include "bar.h"

struct Baz;

class Foo : public Bar
{
private:
  Baz* d_baz;
  
public: 
  int get_info();  
};

#endif // foo_h

Second, a private header file containing all non-public type definitions (foo_i.h). This file can be used by debugging modules that need access to implementation-specific data structures.

This file also contains all inline member functions belonging to classes declared here. (This feature is not shown in the example.)

// Preprocess-generated private header file
#ifndef foo_i_h
#define foo_i_h

struct Baz
{
  int data;
};

#endif // foo_i_h

Third, an implementation file (foo.cc). This file starts with declarations for all free "static" functions (not shown in the example). Otherwise, it comprises all non-inline function definitions (and static inline functions).

// Preprocess-generated implementation file
#include "foo.h"
#include "foo_i.h"

void Foo::get_info() 
{
  return d_baz->info();
}

Options reference

Output files have names of the following form:

where [prepend] is an optional filename part that can be specified using -p, outfile_base needs to be specified using -c, suffix is an optional suffix that can be specified using the IMPLEMENTATION directive, and source_ext and header_ext default to ".cc" and ".h" but can be overridden using -C and -H, respectively.
-c filename_base
Specifies the basename of generated file names that appear in the output files. This option is mandantory.
-o outfile_base
Specifies the basename of output files. It defaults to the value of the -c option.
-s
Generate one implementation file per source file, using the base name of the implementation file as outfile_base, and not one implementation file per suffix specified as arguments to IMPLEMENTATION directives in the source files.
-p prepend
Specifies a prefix for all output file names. This prefix is prepended to filename_base and can contain directory separators ("/").
-C source_ext
Specify the extension of generated implementation files. Defaults to ".cc".
-H header_ext
Specify the extension of generated header files. Defaults to ".h".
-e tag_list
Enable conditional compilation using the selectors included in the comma-separated tag_list. This option is intended to be used in conjunction with the -s option. See Section Conditional compilation for details on this option.
-t
Truncate to size 0 output files for which Preprocess generates no output. This option supresses even #include directives in generated source files that are otherwise empty, resulting in increased compilation speed for these files.
-i
Generate inline code. If this option is not given, all code (including code marked "inline") is generated out-of-line.
-l
Avoid generating #line directives in output files. If this option is not given, #line will be generated by default.
-L
Avoid generating #line directives in header files only. Using this option can speed up builds because the contents of header files change less frequently, as #line directives for (member) function declarations do not have to be updated every time its definition in the source module changes its absolute position. (Of course, this assumes that the time stamp of header files are updated only when the contents of the files change. See Section Example Makefile fragment for a possible way to do this.)
-v
Be verbose: Print results of preprocess' parser pass.
-d
Be verbose: Print a diagnostic when dropping a section in conditional-compilation mode (Option -e).

Language directives

Preprocess understands a number of language directive that control its behavior.

INTERFACE:
Starts an interface section. Every declaration from such a section will be copied to the public header file. Class declarations found here (``public class'') will be completed with member-function declarations for member functions found in IMPLEMENTATION sections.

Function definitions are not allowed in this section.

IMPLEMENTATION:
Starts an implementation section. Preprocess tries to hide declarations found in these sections in the internal header file and in the implementation file, as follows:
Class declarations (``private classes'')
(subject to member-function-declaration completion as with those in INTERFACE: sections) end up in the internal header file -- except if a public inline function of a public class depends on the private class, in which case the private class' declaration will be put into the public header file.
Include directives
underlie the same rules as class declarations.
Function definitions
are usually held in the implementation file. Public inline functions, private inline functions needed by a public inline function, and functions subject to template instatiation are exported to the public header file. Other inline functions (except static non-member inline functions) are put into the private header file.
Other code (e.g., variable definitions)
is put into the implementation file.

PUBLIC, PRIVATE, and PROTECTED
specify member-function visibility.

explicit, static, and virtual
specify member-function attributes. The attributes will be copied to the automatically-created member-function declarations in class declarations (and removed for the actual function definition, as C++ requires).

inline
specifies inline functions. This attribute will be retained for function definitions (but deleted for automatically-created member-function declarations, as C++ requires). (Inline functions will be exported subject to the rules defined above.)

inline NEEDS [dependencies, ... ]
like inline, but additionally specifies types, functions, and #include statements that this inline function depends on and that consequently need to be exported as well, in front of this inline function. Preprocess reorders definitions such that all dependencies are defined before the inline function.

Example:

inline NEEDS["foo.h", some_func, Some_class,
             Some_other_class::member_func]
int
foo ()
{ }

Language directives for advanced use

IMPLEMENTATION [suffix]:
Starts an implementation section with code that will be put into a nonstandard output file. Instead of landing in outfile_base.cc, the code ends up in outfile_base-suffix.cc. This directive is useful if there are several input files that together make up one input module (which are fed to Preprocess at the same time and which share one public and one private header file).

(This form of the IMPLEMENTATION directive works only if neither the -s (no-suffix) nor the -e (conditional compilation) options are used. See Section Conditional compilation for information on conditional compilation.)

EXTENSION class classname { ... };
Extends the definition of class classname (which usually appears in another input file) with more members. This clause is usually used when a class can be configured with several alternative extensions, for example to provide portability across architectures.

IMPLEMENT
is a member-function attribute that specifies that the member function's declaration should not be copied to the class declaration. Use this attribute to implement an interface that is already declared in the class declaration.

inline NOEXPORT
specifies inline functions that will not be exported via the public header file even if it is a publicly-visible function. Instead, the function definition will end up in the implementation file.

inline ALWAYS_INLINE
specifies a functions that is generated as an inline function even if the -i option is not used. Use this specifier for functions that absolutely must be inline even in debugging builds.

Conditional compilation

Conditional compilation is a Preprocess mode that is enabled by using the "-e tag_list" option.

INTERFACE [tag_expression]:
IMPLEMENTATION [tag_expression]:
A tag_expression is a logical expression with negation (!), conjunction (-), disjunction (,), and one level of parentheses ({ and }), using selector tags as its atoms. The INTERFACE or IMPLEMENTATION section is included in the output only if it is true using the selectors specified in tag_list.

Examples:

INTERFACE [a,b]:
// This section is used whenever a or b is contained in the tag_list

INTERFACE [a-b]: 
// This section is used whenever a and b are contained in the tag_list

INTERFACE [a,b-c]:
// This section is used whenever a, or b and c are 
// contained in the tag_list

INTERFACE [!a]:
// This section is used whenever a is not contained in the tag_list

INTERFACE [{a,b}-c]:
// This section is used whenever a and c, or b and c are 
// contained in the tag_list

INTERFACE [!a,b-c]:
// This section is used whenever a is not contained in the tag_list,
// or b and c are contained in the tag_list

Usage hints

When you use Preprocess, there are a few things you need to keep in mind.

Example Makefile fragment

This is an example fragment from a Makefile (for GNU Make) that generates *.cc, *.h, and *_i.h files on the fly. It only updates the generated files if they actually change; that is, if you change something in the implementation section that does not influence the header files, they will not be updated, avoiding recompilation of files that depend on them.

This Makefile fragment needs GNU Make and the move-if-change script that only updates a target if it is different from the source.

This example assumes that you do not use the IMPLEMENTATION[suffix] directive. If you do plan using this directive, a more elaborate mechanism is needed, such as the one used in the Makefiles for the Fiasco microkernel.

PREPROCESS = preprocess

.PRECIOUS: stamp-%.ready
stamp-%.ready: %.cpp
	$(PREPROCESS) -i -o new_$* -c $* $<
	./move-if-change new_$*.h $*.h 
	./move-if-change new_$*_i.h $*_i.h 
	./move-if-change new_$*.cc $*.cc 
	touch $@

%.cc: stamp-%.ready
        @[ -e $@ ] || { rm -f $<; $(MAKE) $<; }

%.h: stamp-%.ready
        @[ -e $@ ] || { rm -f $<; $(MAKE) $<; }

%_i.h: stamp-%.ready
        @[ -e $@ ] || { rm -f $<; $(MAKE) $<; }

Limitations and ideas for future extensions

Bugs

Ideas for future extensions

Download

Preprocess is free software licensed under the GNU General Public License. Its implementation language is Perl, so you need a Perl5 interpreter to run it.

You can download Preprocess as CVS module "preprocess" from the DROPS project's remote-CVS server. Please refer to the download instructions on DROPS' website.

Mailing list

There is a mailing list to which CVS-commit messages for changes made to preprocess are posted. Please ask me if you would like to be put on this list (see Section Author ).

New releases are periodically announced on the Freshmeat website. If you are a registered Freshmeat user, you can subscribe to these release announcements.

Author

Michael Hohmuth <hohmuth@inf.tu-dresden.de>

See also

move-if-change(1) shell script

Preprocess was originally written for the Fiasco microkernel.


Generated on Tue Apr 26 13:43:16 2005 for Preprocess by doxygen1.2.15