Literate Programming and Code Reuse

"Literate programming," a concept introduced by Donald Knuth, lets you mix code and documentation in the layout of a program. Sverre implements the Singleton and State patterns to combine a macro processor with literate programming techniques.


June 01, 1997
URL:http://www.drdobbs.com/tools/literate-programming-and-code-reuse/184410206

Dr. Dobb's Journal June 1997: Literate Programming and Code Reuse

Sverre is software manager at TTS Automation in Bergen, Norway, and can be reached at [email protected]. TTS makes robot production systems for the shipyard industry.


The term "Literate Programming" describes the concept of mixing code and documentation introduced by Donald Knuth. The original system, "WEB system of structured documentation," was developed to provide the basis for the documentation/implementation of the TeX and MetaFont programs (see Knuth's "Literate Programming," Center for the Study of Language and Information (CSLI) Lecture notes number 27, 1992). WEB consists of two filters working on WEB source code; Tangle extracts compilable Pascal code, while Weave extracts the TeX-based documentation of the program.

In this article, I'll look at ways in which literate programming helps you reorganize the layout of a program. When given the freedom to structure program layout as you wish, you tend to group parts of the code differently than as dictated by the language or compiler. Rather than grouping all public-function declarations of a C++ class together in the public section of the class, for instance, you might group each function declaration with the corresponding function implementation. I'll also examine how some of these new groups of code can be parameterized into reusable macros with the help of a simple macro processor. Specifically, I'll show how the application of design patterns such as Singleton and State from Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma et al. (Addison-Wesley, 1995), can be supported by such macros.

This article is a literate program in itself. The first part (available electronically; see "Availability," page 3) documents and implements a set of reusable macros, while the last part is an example of how these macros are used. Extracted C++ code from the example section of this article is presented in Listings One and Two The tools and techniques described in this article have been used daily at TTS Automation for the past year and a half for the development of our offline robot programming systems.

The CLiP System for Literate Programming

The CLiP program (which was developed by E.W. van Ammers and M.R. Kramer and is available at ftp://sun01.info.wau.nl/clip/ for DOS, VMS, and UNIX) reads any number of ASCII files that have been extracted from documents that contain the program code and documentation (written with a word processor). From these, CLiP (short for "Code from Literate Programming") generates corresponding "source" files. Since CLiP is language independent, source files, make files, scripts/batch files, test data and the like can be generated from the same document(s).

Two terms are central to understanding CLiP:

1. A "stub" is a piece of text (code) copied to the output file. It is enclosed in the document with lines like:

/*** <Name of Stub> ***/

and

/*** End of <Name of Stub> ***/

2. A "slot" is a special line that can occur inside of any stub. It looks like the opening line of a stub and serves as a placeholder for the stub with the same name. The slot line will be replaced by the contents of this stub in the output file.

Both stubs and slots can have option specifiers prefixed with "#". The most important stub option is #file. The file stub has no stub name, but its contents are copied to the specified file:

/*** #file "extract.bat" #comment off ***/
rem Run the macro processor 
clipprep c:\litprog\litprog.txt >tmp.txt
rem Run CLiP
clp_env . . clp tmp.txt/*** End of File ***/

The #comment option turns on or off comments generated by CLiP. The stub option #quick indicates that the stub is ended by the next blank line rather than the "End of" line.

The most frequently used slot option is #multiple (#mult), which allows more than one stub to be substituted for the slot. The #optional (#opt) slot option suppresses the warning if there is no stub to fit in a slot.

Stubs can come in any order in the CLiP source file. In a C++ class definition, for example, the member function declaration and its definition can be written in the same section. Alternatively, a data member declaration, its initialization in the constructor, the corresponding statements in the copy constructor and assignment operator, its access functions, and its destruction in the class's destructor can be placed in the same section of the source file. Listing Three is a CLiP source file with the corresponding generated code.

When given the freedom to reorganize the layout of a program like this, it becomes apparent that structures -- other than those traditionally thought of as reusable -- repeat themselves. These range from trivialities like source file and class layout via member-variable handling, to complex design patterns like those described in Design Patterns or Jim Coplien's Advanced C++: Programming Styles and Idioms (Addison-Wesley, 1994).

The Macro Preprocessor

To enable reuse of these structures, you need a macro processor. The one I use, CLiPPrep (which is also available electronically), is a filter, expanding macros of the forms:

The macro call will be substituted by the macro's body. A macro parameter in this form may contain any characters except "," and ")". If these characters are needed in the parameter, you must enclose the parameter with named brackets of the form %x{ and %x}, where x can be any letter or digit, or can be omitted. These brackets must be matched and may not contain any closing brackets with the same name. This is a legal macro call:


%OneParameterMacro(%a{
This parameter may contain ',' and ')'
It also contains three EndOfLines.
%a})

One of the predefined macros is DefMacro, which registers a new macro. It takes at least two parameters, the first being the macro name, and the last the macro body. The optional middle parameters are the parameter names of the new macro. These are referenced from the macro body like parameterless macros.

Below is a macro that defines the layout of a C++ class declaration. A class has a name and may inherit another class. I assume only public and nonmultiple inheritance to keep the examples simple.


%DefMacro(Class,class,baseClass,%{
class %class %If(%baseClass,: public %baseClass,){
public:
 /*** %class public props #mult #opt ***/
protected:
 /*** %class protected props #mult #opt ***/
private:
 /*** %class private props #mult #opt ***/
};
%})

If is a predefined macro that evaluates to one out of two bodies depending on whether or not its first parameter is the empty string. The call %Class(CMyClass,CObject) expands to:


class CMyClass : public CObject {
public:
 /*** CMyClass public props #mult #opt ***/
protected:
 /*** CMyClass protected props #mult #opt ***/
private:
 /*** CMyClass private props #mult #opt ***/
};

The class's properties will be filled in by stubs elsewhere in the document.

Reusing the Repeating Structures

Before describing how design patterns are supported, I need to define two more macros.

DefineClass defines corresponding header and implementation files for a class, using the previous Class macro for the class declaration itself. It takes the filename base, class name, and base class name as parameters. The header file contains one slot for includes, one for declarations, and the class declaration itself -- everything surrounded by an #ifndef statement. The implementation file includes the corresponding header file and has a slot for its own includes, and a slot for the implementation itself:


%DefMacro(DefineClass,file,class,baseClass,%a{
%%% Make the header file
/*** #file "%file.hpp" #comment off ***/
#ifndef INCLUDE_%file
#define INCLUDE_%file
/*** %class header includes #mult #opt ***/
/*** %class declarations #mult #opt ***/
%Class(%class,%baseClass)
#endif
/*** End Of File ***/


%%% Now for the implementation file
/*** #file "%file.cpp" #comment off ***/
#include "%file.hpp"
/*** %class implementation includes #mult #opt ***/
/*** %class implementation #mult #opt ***/
/*** End Of File ***/
%a})

The second basic macro is the member function. A member function is contained in a class and has a name, access (public, protected, private), parameters, return value, possibly a type modifier (virtual or static), and body. Again, to keep the example simple, I ignore pure virtual functions, overloading, default values of parameters, const functions, and the like. The macro puts the function declaration into the correct slot in the class declaration, and the function implementation into the implementation file, opening a "<functionName> actions" slot for the function body:

%DefMacro(Method,class,scope,typeMod,type,name,pars, %a{ %%% Make the prototype /*** %class %scope props ***/ %typeMod %type %name (%pars); /*** End of %class %scope props ***/ %%% Make the definition /*** %class implementation ***/ %type %class::%name (%pars){ /*** %class::%name actions #mult #opt ***/ } /*** End of %class implementation ***/ %a})

The Singleton Pattern

By protecting the constructor and letting all access to the object go through a static member function, the Singleton pattern ensures that there is only one instance of a class created. The following macro adds a private constructor to the class, a private static object pointer to the single instance of the class, and a static public member function Instance() that returns the instance pointer, if necessary, after creating the object.


%DefMacro(MakeSingleton,class,
%a{
%%% The private constructor
%Method(%class,private,,,%class,)
%%% The static instance pointer
/*** %class private props ***/
static %class * m_instance;
/*** End of %class private props ***/
%%% ...and its definition
/*** %class implementation ***/
%class * %class::m_instance = NULL;
/*** End of %class implementation ***/
%%% The Instance() function
%Method(%class,public,static,%class *,Instance,)
%%% and its body
/*** %class::Instance actions ***/
if(m_instance == NULL){
 m_instance = new %class;
}
return m_instance;
/*** End of %class::Instance actions ***/
%a})

This is a simple macro to build; it adds a set of features to an already existing class. Listing Four is the result of the two macro calls, partly evaluated by CLiP:


%DefineClass(mysing,CMySingleton,)
%MakeSingleton(CMySingleton) 

The Abstract Base Class

When you have several classes that should implement the same interface, or if you want to achieve a proper division between interface and implementation for one class in C++, you define the interface as an abstract base class (ABC) which the implementation classes inherit. I will describe a variant of this structure where the subclasses are declared in the implementation file of the base class; that is, they are completely invisible from the client side. Instances of these classes might, for example, be created through static functions in the abstract base class.

This is more a feature of the C++ language than an idiom or design pattern, but it occurs frequently and contains features coded in its layout (the concrete classes are invisible because they are declared in the implementation file). Macros can also help ensure consistency between the base class and concrete classes. Therefore, this structure is a good candidate to implement as a reusable macro. Listing Five presents the layout of the header and implementation file.

Four macros describe this structure:

1. ABC(class) prepares the given class as an abstract base class, giving it a virtual destructor and partitioning the implementation file:


%DefMacro(ABC,class,%{
%%% The virtual destructor
%Method(%class,public,virtual,,~%class,)


%%% The slot for the sub-class declarations
/*** %class implementation ***/
/*** %class derived classes #mult #opt ***/
/*** %class child implementations #mult #opt ***/
/*** End of %class implementation ***/
%})

2. ABCMethod(class,returnType,name,parameters) adds the specified method to the interface as a pure virtual function and remembers (defines as macros) the return value and parameters:


%DefMacro(ABCMethod,class,type,name,pars,%{
%%% Define the method
/*** %class public props ***/
virtual %type %name (%pars) = 0;
/*** End of %class public props ***/
%%% Remember the return type and parameters.
%DefMacro(%name ReturnType,%type)
%DefMacro(%name Parameters,%pars)
%})

3. ABCDerivedClass(baseClass,class) defines an implementation class inheriting the ABC and defines the slot for its implementation:


%DefMacro(ABCDerivedClass,baseClass,class,%a{
%%% The subClass
/*** %baseClass derived classes ***/
%Class(%class,%baseClass)
/*** End of %baseClass derived classes ***/


%%% Make the slot for the sub-class implementation
/*** %baseClass child implementations ***/
/*** %class implementation #mult #opt ***/
/*** End of %baseClass child implementations ***/
%a})

4. ABCDerivedClassMethod(class,name) looks up the stored return value and the parameters of the method and defines them in the class:


%DefMacro(ABCDerivedClassMethod,class,name,
%x{
%Method(%class,public,virtual,
 %{%Eval(%% %name ReturnType)%},%name,
 %{%Eval(%% %name Parameters)%})
%x})

The Eval macro takes one parameter and evaluates it one extra time, and the "%%" constellation evaluates to a single "%".

Remember that function bodies, member variables, helper functions, and the like are added to the classes in the normal manner, using the Method macro or writing stubs that fit in the appropriate slots.

The State Pattern

You can organize the state-dependent behavior of a class, (the "context") by forwarding requests for this behavior to a contained instance of a State class. This State class is an abstract base class and the instance variable is changed to point to different implementations for different states. Here, I'll describe a variant of this pattern, where the state objects do not have data members. The context's "this" pointer can, however, be passed with every function call to the state objects so that they can access the context's data if necessary. The concrete state classes are thereby modeled as Singletons. This pattern is divided into two macros:

1. AddState(contextClass,stateId) adds a state variable to the context class and defines the state ABC. It remembers the context class corresponding to the stateId (sId). The state class name is chosen to be the stateId prefixed by "C":


%DefMacro(AddState,context,sId,
%{
%%% Define the State Base Class in the 
%%% implementation file of the context.
/*** %context implementation ***/
%Class(C%sId,)
/*** C%sId implementation #mult #opt ***/
/*** End of %context implementation ***/
%%% Make it an ABC
%ABC(C%sId)
%%% We have to declare it.
/*** %context Declarations ***/
class C%sId;
/*** End of %context Declarations ***/


%%% Making the variable
/*** %context private props ***/
C%sId * m_%sId;
/*** End of %context private props ***/


%%% Remember the context corresponding to the sId
%DefMacro(%sId Context,%context)
%})

2. DefineState(sId,stateName) defines a class that inherits the state base class. It calls ABCDerivedClass and makes it a Singleton. It is also a friend of the context.


%DefMacro(DefineState,sId,name,%a{
%ABCDerivedClass(C%sId,%name)
%MakeSingleton(%name)
/*** %Eval(%% %sId Context) public props ***/
friend class %name;
/*** End of %Eval(%% %sId Context) public props ***/
%a})

The macros ABCMethod and ABCDerivedClassMethod are used to add methods to the interface and to the concrete states.

Using the Macros

Assume that, up to this point, this article is in a separate file and is maintained as a reusable module. The text is included in this section by the macro processor's Include(fileName) macro. The code generated from this part of the article is presented in Listings One and Two.

Assume an application comprised of a number of geometric entity classes that know how to draw themselves as wireframes, in terms of functions offered by the CGl class. The entity instances may be ordered hierarchically in that more-primitive or lower-level entities may be a part of a higher-level entity.

The CGl class makes the interface to the OpenGL graphics library and implements the functions relating to color control. Consequently, you want the following functionality:

The functions PushColor, PopColor, LockColor, and UnlockColor perform these tasks. Each works differently, depending on whether the color is in the state "Locked" or "Normal".

I'll start by defining the CGl class, states, and interface functions:


%DefineClass(glclass,CGl,CObject)
%%% Add Some Includes
/*** CGl header includes ***/
#include <afxwin.h>
#include <gl.h>
#include "ccolor.hpp"
/*** End of CGl header includes ***/
%%% Add the state variable and the states
%AddState(CGl,ColorMode)
%DefineState(ColorMode,CLocked)
%DefineState(ColorMode,CNormal)
%%% Then the four methods
%ABCMethod(CColorMode,void,PushColor,%{
 CGl * pC,
 const CColor & color
%})
%ABCMethod(CColorMode,void,PopColor,CGl * pC)
%ABCMethod(CColorMode,void,LockColor,CGl * pC)
%ABCMethod(CColorMode,void,UnlockColor,CGl * pC)

The start state must be set. Since the constructor of CGl is not already defined, you must do that too:


%Method(CGl,public,,,CGl,)


/*** CGl::CGl actions #quick ***/
m_ColorMode = CNormal::Instance();

Use the following code to keep track of a stack of colors, and the number of times LockColor is called:


/*** CGl private props #quick ***/
int lockLevel;
CObList colorList; // Used for implementing a stack

PushColor and PopColor do nothing in Locked mode, but must maintain the stack of colors and set the current color in Normal mode:


%ABCDerivedClassMethod(CLocked,PushColor)
%ABCDerivedClassMethod(CLocked,PopColor)

%ABCDerivedClassMethod(CNormal,PushColor) /*** CNormal::PushColor actions #quick ***/ pC->colorList.AddHead(new CColor(color)); glColor3d(color.R(),color.G(),color.B());

%ABCDerivedClassMethod(CNormal,PopColor) /*** CNormal::PopColor actions #quick ***/ delete pC->colorList.RemoveHead(); CColor * pColor = (CColor *) pC->colorList.GetHead(); glColor3d(pColor->R(),pColor->G(),pColor->B());

LockColor switches modes and initiates the lockLevel to 1 (if in Normal mode) and increments the lockLevel (if in Locked mode):


%ABCDerivedClassMethod(CNormal,LockColor)
/*** CNormal::LockColor actions #quick ***/
pC->lockLevel = 1;
pC->m_ColorMode = CLocked::Instance();


%ABCDerivedClassMethod(CLocked,LockColor)
/*** CLocked::LockColor actions #quick ***/
pC->lockLevel++;

UnlockColor should decrement the lockLevel and change mode if it reaches zero while in Locked mode, and assert if in Normal mode:


%ABCDerivedClassMethod(CNormal,UnlockColor)
/*** CNormal::UnlockColor actions #quick ***/
ASSERT(0);

%ABCDerivedClassMethod(CLocked,UnlockColor) /*** CLocked::UnlockColor actions #quick ***/ pC->lockLevel--; if(pC->lockLevel == 0){ pC->m_ColorMode = CNormal::Instance(); }

Now you need to to reflect these functions out to the CGl class:


%Method(CGl,public,,void,PushColor,const CColor & c)
/*** CGl::PushColor actions #quick ***/
m_ColorMode->PushColor(this,c);

%Method(CGl,public,,void,PopColor,) /*** CGl::PopColor actions #quick ***/ m_ColorMode->PopColor(this);

%Method(CGl,public,,void,LockColor,) /*** CGl::LockColor actions #quick ***/ m_ColorMode->LockColor(this);

%Method(CGl,public,,void,UnlockColor,) /*** CGl::UnlockColor actions #quick ***/ m_ColorMode->UnlockColor(this);

Conclusion

Traditional drawbacks to literate programming include:

Still, you get a system that encourages and enables good source-code documentation and intuitive layout.

The concept of combining a macro processor with literate programming is very expressive. For C++, it surpasses the expressiveness of templates. It is also applicable to most other programming languages. Weaknesses are mainly related to the correctness of the extraction. For example, even if you incorrectly name a stub, you may not get a warning, and the resulting modules may still compile.

For more information on literate programming, see the comp.programming.literate FAQ at ftp://ftp.th-darmstadt.de/ pub/programming/literate-programming and the author's home page at http://home.sol.no/tts/sh.

DDJ

Listing One

#ifndef INCLUDE_glclass#define INCLUDE_glclass
#include <afxwin.h>
#include <gl.h>
#include "ccolor.hpp"
class CColorMode;


class CGl: public CObject{ public: friend class CLocked; friend class CNormal; CGl(); void PushColor(const CColor & c); void PopColor(); void LockColor(); void UnlockColor(); protected: private: CColorMode * m_ColorMode; int lockLevel; CObList colorList; // Used for implementing a stack }; #endif

Back to Article

Listing Two

#include "glclass.hpp"

class CColorMode{ public: virtual ~CColorMode(); virtual void PushColor( CGl * pC, const CColor & color ) = 0; virtual void PopColor(CGl * pC) = 0; virtual void LockColor(CGl * pC) = 0; virtual void UnlockColor(CGl * pC) = 0; protected: private: }; CColorMode::~CColorMode(){ } class CLocked: public CColorMode{ public: static CLocked* Instance(); virtual void PushColor( CGl * pC, const CColor & color ); virtual void PopColor(CGl * pC); virtual void LockColor(CGl * pC); virtual void UnlockColor(CGl * pC); protected: private: CLocked(); static CLocked* m_instance; }; class CNormal: public CColorMode{ public: static CNormal* Instance(); virtual void PushColor( CGl * pC, const CColor & color ); virtual void PopColor(CGl * pC); virtual void LockColor(CGl * pC); virtual void UnlockColor(CGl * pC); protected: private: CNormal(); static CNormal* m_instance; }; CLocked::CLocked(){ } CLocked* CLocked::m_instance = NULL; CLocked* CLocked::Instance(){ if(m_instance == NULL){ m_instance = new CLocked; } return m_instance; } void CLocked::PushColor( CGl * pC, const CColor & color ){ } void CLocked::PopColor(CGl * pC){ } void CLocked::LockColor(CGl * pC){ pC->lockLevel++; } void CLocked::UnlockColor(CGl * pC){ pC->lockLevel--; if(pC->lockLevel == 0){ pC->m_ColorMode = CNormal::Instance(); } } CNormal::CNormal(){ } CNormal* CNormal::m_instance = NULL; CNormal* CNormal::Instance(){ if(m_instance == NULL){ m_instance = new CNormal; } return m_instance; } void CNormal::PushColor( CGl * pC, const CColor & color ){ pC->colorList.AddHead(new CColor(color)); glColor3d(color.R(),color.G(),color.B()); } void CNormal::PopColor(CGl * pC){ delete pC->colorList.RemoveHead(); CColor * pColor = (CColor *) pC->colorList.GetHead(); glColor3d(pColor->R(),pColor->G(),pColor->B()); } void CNormal::LockColor(CGl * pC){ pC->lockLevel = 1; pC->m_ColorMode = CLocked::Instance(); } void CNormal::UnlockColor(CGl * pC){ ASSERT(0); } CGl::CGl(){ m_ColorMode = CNormal::Instance(); } void CGl::PushColor(const CColor & c){ m_ColorMode->PushColor(this,c); } void CGl::PopColor(){ m_ColorMode->PopColor(this); } void CGl::LockColor(){ m_ColorMode->LockColor(this); } void CGl::UnlockColor(){ m_ColorMode->UnlockColor(this); }

Back to Article

Listing Three

----- ccolor.doc ----------------A class that stores the RGB values of a color.
/*** #file "ccolor.hpp" #comment off ***/
class CColor: public CObject {
public: 
  /*** CColor public props #mult #opt ***/
private: 
  /*** CColor private props #mult #opt***/
};
/*** End of File ***/


The data members: /*** CColor private props #quick ***/ int m_r; int m_g; int m_b;

We need a constructor that allows setting these; /*** CColor public props #quick ***/ CColor(int r,int g,int b);

/*** CColor public props #quick ***/ int R()const;int G()const;int B()const; --------------------------

----- ccolor.hpp --------------- class CColor: public CObject { public: CColor(int r,int g,int b); int R();int G();int B(); private: int m_r; int m_g; int m_b; }; --------------------------

Back to Article

Listing Four

----- mysing.hpp: ----------#ifndef INCLUDE_mysing
#define INCLUDE_mysing


/*** CMySingleton header includes #mult #opt ***/ /*** CMySingleton declarations #mult #opt ***/ class CMySingleton{ public: static CMySingleton* Instance(); /*** CMySingleton public props #mult #opt ***/ protected: /*** CMySingleton protected props #mult #opt ***/ private: CMySingleton(); static CMySingleton* m_instance; /*** CMySingleton private props #mult #opt ***/ }; #endif -------------------------

----- mysing.cpp: ---------- #include "mysing.hpp" /*** CMySingleton implementation includes #mult #opt ***/

CMySingleton::CMySingleton(){ /*** CMySingleton::CMySingleton actions #mult #opt ***/ } CMySingleton* CMySingleton::m_instance = NULL;

CMySingleton* CMySingleton::Instance(){ if(m_instance == NULL){ m_instance = new CMySingleton; } return m_instance; } /*** CMySingleton implementation #mult #opt ***/ -------------------------

Back to Article

Listing Five

----- myabc.hpp: ----------#ifndef INCLUDE_myabc
#define INCLUDE_myabc


/*** CABC header includes #mult #opt ***/ /*** CABC declarations #mult #opt ***/ class CABC{ public: virtual ~CABC(); /*** CABC public props #mult #opt ***/ protected: /*** CABC protected props #mult #opt ***/ private: /*** CABC private props #mult #opt ***/ }; #endif -------------------------

----- myabc.cpp: ---------- #include "myabc.hpp" /*** CABC implementation includes #mult #opt ***/ CABC::~CABC(){ } /*** CABC derived classes #mult #opt ***/ /*** CABC child implementations #mult #opt ***/ /*** CABC implementation #mult #opt ***/ -------------------------

Back to Article


Copyright © 1997, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.