Assume you are building a C++ representation of a zoo. You will likely make up a top-level C++ class called Zoo, which has a list of animals. An animal itself should probably be a pure virtual superclass which has derived instantiable classes such as Elephant. A common strategy is to make up one file per class, so you'll have e.g. ZrZoo.h, ZrAnimal.h, ZrElephant.h (Zr for "zoo representation" is a representation-specific prefix to the classes to avoid name clashes with other C++ libraries; this will become unnecessary once the C++ "namespace" construct is widely implemented). There is a lot of redundancy in these classes. For example, most classes should have a standard constructor, a destructor, a copy constructor, an assignment operator, access functions to its data members, comparison functions, and a print function for debugging purposes. Each header file should be protected against multiple inclusion with the standard ifndef/define/endif preprocessor directive. Pure virtual base classes should have a copy() function that acts as the virtual constructor that C++ does not offer, derived leaf classes should declare all functions required by its pure virtual superclasses, copy constructors should be public for leaf classes but protected for base classes, and so on. In Meta-C++, implementing this representation is done by writing a representation meta-model of these classes, which will result in this code being written out for you.
Finally, you often want to be able to fill in the representation from a plain text file, by writing a parser for the representation. This is best done using a lexical analyzer generator and a parser generator, such as the GNU utilities flex and bison. In Meta-C++, such parsing can be implemented from a single specification called the grammar meta-model.
_prefix "Zr"
Animal {STRING name}
Mammal : Animal {}
Elephant : Mammal {}
Snake : Animal {BOOL isVenomous}
Animals : LIST<Animal> {}
Employee {STRING firstName, STRING lastName}
Employees : LIST<Employee> {}
Zoo {STRING name,Animals,Employees}
_for Zoo {
_enable 'Zoo';
_enable 'replaceName'; _enable 'replaceAnimals'; _enable 'replaceEmployees';
}
The meta-model generator will produce seven header files and seven ".cc" files that completely implement this representation in C++. Omitting the name of an instance variable as done in class Zoo will result in a member name that is the same as the member class, with a lower-case first character. The first line of the meta-model above specifies a prefix to be added to class names and file names of the generated code as to not cause conflicts with other libraries.
class ZrZoo {
string* iName;
ZrAnimals* iAnimals;
ZrEmployees* iEmployees;
...
Each class receives a standard constructor, a parser constructor, a destructor, a copy constructor, and an assignment operator. The standard constructor has one argument for each of the (possibly inherited) data members of the class. A standard constructor fills in the data members by copying from the arguments. For class ZrZoo above, the function signature of the standard constructor is:
ZrZoo::ZrZoo(const char* /*name*/,const ZrBAnimals&,const ZrBEmployees&);The parser constructor takes pointers to allocated data structures instead, and copies only the pointers from its arguments. It is called a "parser" constructor because it allows a parser to define one rule for each class that returns an allocated instance of the class, and to then build up the complete structure recursively. (The parser constructors can also be used - and be useful - in other code, of course.) For ZrZoo, the function signature of the parser constructor is:
ZrZoo::ZrZoo(string* /*name*/,ZrAnimals*,ZrEmployees*);The destructor deallocates the data members of the class as you would expect. The copy constructor makes a deep copy of its argument. The assignment operator recursively calls the assignment operators on its superclasses and own data members, also resulting in a deep copy (there are no pointers into data structures of the argument at the end).
ZrZoo::~ZrZoo(); ZrZoo::ZrZoo(const ZrZoo&); ZrZoo::operator=(const ZrZoo&);An inlined access function is generated for each data member (unless you prevent its generation with a _prevent directive, see below). For the ZrZoo class, that will be:
inline const string& getName() {return *iName;}
inline const ZrAnimals& getAnimals() {return *iAnimals;}
inline const ZrEmployees& getEmployees() {return *iEmployees;}
While the representation meta-model generator "knows" how to produce several different modification functions for data members it does not write any of them out unless you explicitly ask for them with an "_enable" directive (see Section 2.2.3). The reason for this is that a class design where every individual data member can be manipulated from the outside is usually a bad idea.Two comparison operators are implemented for each class. Class a is equal to class b if all of its data members are equal. Similarly, class a is "less than" class b if all of its data members are.
int/*bool*/ ZrZoo::operator==(const ZrZoo&) const; int/*bool*/ ZrZoo::operator<(const ZrZoo&) const;Finally, there is a "print" function for debugging purposes. If you print a class, it will print out itself and all its data members. The C++ output operator also uses this function.
void ZrZoo::print(ostream&) const; friend ostream& operator<<(ostream&,const ZrZoo&);
A list class is a class with a LIST template as its only superclass, such as ZrAnimals and ZrEmployees above. A list class cannot have any other superclasses, nor can it have any data members. In the generated code for list classes, there are actually three classes defined - one list class which owns its members (dealloacates them when it goes out of scope), one list class which just points to its members, and one which is a common base class for the former two. For list class X, these classes are named X, PX and BX, respectively. (For class Animals in the representation meta-model, the classes ZrBAnimals, ZrAnimals and ZrPAnimals are generated.) In this scheme, the ZrZoo uses a ZrAnimals (the zoo "owns" its animals) but your user-defined code may use a ZrPAnimals to temporarily point to, say, the animals bought by the zoo since the beginning of the year (no need to make deep copies of ZrAnimal here). In addition, you can define functions that apply to both kinds of lists in the base class (say one which finds an animal by its name).
A dictionary class is a class with a DICTIONARY template as its only superclass. Dictionaries are hash-based structures from its first to its second argument. For example, the zoo representation may want to declare the following class if the number of its animals is very large and they are often retrieved by name: "AnimalDict: DICTIONARY<STRING,Animal*>;". Otherwise, dictionaries behave much as lists do.
A standard class is a class which is not a list or dictionary class and that does not inherit from any other local class and that no other local class inherits from. ZrEmployee and ZrZoo itself are such classes in the zoo representation. For a standard class, its constructors, destructor and assignment operator are public, and an output operator is declared.
A base class is a class which is not a list or dictionary class that at least one other local class inherits from but that does not itself inherit from any local class. In the zoo representation, ZrAnimal is such a class. Base classes receive and enumeration member of type "CmmType" that lists all possible leaves of the base class.
public: enum CmmType {snake,elephant};
private: CmmType iCmmType;
public: inline CmmType getCmmType() const {return iCmmType;}
This enumeration can be used for run-time type identification (generating this enumeration will become unnecessary once the C++ run-time type identification is widely implemented). Base classes protect their constructors and assignment operator - they cannot be instantiated (they are always pure virtual classes). They receive a pure-virtual function called "copy" that is used as the virtual copy constructor that C++ does not offer. Its comparison operators and print function are also pure-virtual. The output operator is implemented for a base class (it then does not need to be implemented by any of its derived classes). A base class implements three protected functions that will be used by derived classes: cmmEqualTo, cmmSmallerThan, and cmmPrintForSubclasses.An intermediate class is a class that inherits from a local class and that is inherited by a local class. ZrMammal is such a class in the zoo representation. An intermediate class protects it constructors and assignment operator just as a base class does, and it also declares the three protected functions cmmEqualTo, cmmSmallerThan, and cmmPrintForSubclasses for its subclasses.
A leaf class is a class that inherits from a local non-template class but that nobody else inherits from. Elephant and Snake are leaf classes in the zoo representation. A leaf class implements all virtual functions declared by its base class (copy, print, operator==, operator<), and can be instantiated.
An enumeration class is a class that is explicitly declared to be one. It contains a single data member, which is an enumeration of its values, and related functions. Enumeration classes cannot take part in an inheritance tree. The syntax for an enumeration class is shown below.
_enum MembershipKind {voting,nonvoting,student};
_for Employee {
_add STRING 'concatenatedName'() const;
}
This adds this function signature to the header file. You then implement this function in C++ in some ".cc" file of your choosing and link it in. My convention is that these functions go into a file with the suffix ".user.cc". In this case, here is how ZrAnimal.user.cc could look like.
#include "ZrAnimal.h"
string ZrAnimal::concatenatedName() const {return getFirstName()+getLastName();}
Added functions are "public" in the C++ sense by default. You can also add a protected or private keyword immediately after the _add keyword to indicate otherwise. (Note that keywords of the representation model that are not also C++ keywords are pre-pended with an underscore, others are not.)For base classes, intermediate classes and collection classes, you can declare added functions to be "passed on". If a function is passed on from a base class or intermediate class, it will appear as virtual in that class (or as pure virtual if you add =0 to the function signature) and as non-virtual in all leaf classes reached from that class. If a function is passed on from a collection class (list class or dictionary class), it will be passed on to the item class of this collection (and propagate further is the item class is a base or intermediate class as described above). For collection classes only, you can tell the generator to implement the function which will result in a function that iterates over the items, calling the same function there. Here is an example of passing on a function in the zoo representation.
_for Employees {
_add _passOn _implement costOfLivingAdjustment(INT "percentage points");
}
_for Employees {
_dontImplement 'print';
}
(You will then implement this function yourself, in the ZrEmployees.user.cc file.)There are also certain functions that the generator knows how to create but does not unless you ask it to. These are:
_for Zoo {
_enable 'Zoo';
_enable 'setEmployees';
_enable 'getEmployeesForMod;
}
rmm <zoo.rmm
In addition to the C++ code written out for each class of the representation meta-model there is also a file that defines three types of file names. First, externally visible header files; second, header files that are only internally used (internal classes are declared by having an "_internal" specifier right before the class name in the representation meta-model); and third, the file names for user code.
The file name of the file containing these three macros is derived by concatenating the representation-specific prefix ("Zr" in our zoo example) with the suffix ".make". The names for the three macros are derived by concatenating the upper-case version of the prefix (e.g. "ZR") with "_EXTERNAL_H", "_INTERNAL_H", and _USER_CC", respectively.
The first two types of file names receive ".h" endings while the third receives ".user.cc" suffixes (as there are corresponding ".cc" files for the former, but there is no header file for the latter). A class appears in the ".user.cc" macro if and only if there is user code to be written for this class (because either you have explicitly specified user-defined functions via an "add" directive or implicit functions have been passed on to this class (see Section 2.2.1 for a discussion of pass-on functions)). To take advantage of the third macro in your make file, you must adhere to the convention of putting user code for class x in a "x.user.cc" file.
_add VOID 'computeInitialScreen'(IrDynamicForm&,GrParseTree&) const;
_for Elements {
_add _passOn _implement VOID 'setLeft' (const IrDynamicForm&, INT "left");
}
Grammar "zoo"
Prefix "Zoo"
doubleQuotedString --> (\"[^"]*\")
integer --> ([0-9]+)
<ZrZoo> zoo: name 'Animals' animals 'Employees' employees
@$TARGET.replaceName($1);
$TARGET.replaceAnimals($3);
$TARGET.replaceEmployees($5);@;
<string> name: 'Zoo' sDoubleQuotedString @$$=$2@;
<ZrElephant> elephant: 'Elephant' '(' sDoubleQuotedString ')' @$$=new ZrElephant($3)@;
<ZrSnake> snake: 'Snake' '(' sDoubleQuotedString ',' sInteger ')' @$$=new ZrSnake($3,$5)@;
<ZrMammal> mammal: elephant @$$=$1@;
<ZrAnimal> animal: mammal @$$=$1@ | snake @$$=$1@;
<ZrAnimals> animals: @$$=new ZrAnimals@ | animal animals @$2->push($1);$$=$2@;
<ZrEmployee> employee: 'first' sDoubleQuotedString 'last' sDoubleQuotedString
@$$=new ZrEmployee($2,$4)@;
<ZrEmployees> employees: @$$=new ZrEmployees@ | employee employees @$2->push($1);$$=$2@;
<string> sDoubleQuotedString: doubleQuotedString
@string tmp($YYTEXT);$$=new string(tmp(1,tmp.length()-2))@;
<int> sInteger: integer
@$$=MmUtUtilities::stoi($YYTEXT)@;
The grammar and prefix name are required, the grammar name should match the name of the file containing it (here: "zoo" because the file is named "zoo.gmm"). The prefix avoids name clashes with other libraries, similar to the one in representation meta-models The C++ class name of the generated parser will be "ZooParser" in this example (concatenation of the prefix with the string "Parser").The next two lines are lexical definitions of tokens which would usually go in a ".l" file. The remaining lines describe rules which would usually go into a ".y" file.
If you compile the code generated for the zoo representation and grammar meta-models, the following piece of code will read in and print a zoo.
int main()
{
ZrZoo z;
ZooParser p;
string error = p.parse(cin,z);
if(error!="") {
cerr << error << "\n"; return 1;
} else {
cout << z << "\n";
}
return 0;
}
Given a zoo text file in the format specified below...
Zoo "Municipal Zoo"
Animals
Snake ("Herbert",1)
Snake ("Eddie",0)
Elephant ("David")
Employees
first "Pedro" last "Szekely"
first "Martin" last "Frank"
... the output of the program is below.
0x7b033950 ZrZoo (
name 0x4000dbc8 STRING "Municipal Zoo"
animals 0x4000e0a8 ZrAnimals (
0x4000dbb0 ZrSnake (
0x4000dbb0 ZrAnimal (
name 0x4000e000 STRING "Herbert"
)
isVenomous BOOL 1
)
0x4000e050 ZrSnake (
0x4000e050 ZrAnimal (
name 0x4000e030 STRING "Eddie"
)
isVenomous BOOL 0
)
0x4000e088 ZrElephant (
0x4000e088 ZrMammal (
0x4000e088 ZrAnimal (
name 0x4000e068 STRING "David"
)
)
)
)
employees 0x400101c0 ZrEmployees (
0x40010138 ZrEmployee (
firstName 0x40010118 STRING "Pedro"
lastName 0x40010108 STRING "Szekely"
)
0x40010160 ZrEmployee (
firstName 0x40010178 STRING "Martin"
lastName 0x40010198 STRING "Frank"
)
)
)
cerr << "l is now: " << l << "\n";Another type of statement just shows that particular points have been reached, especially to find the statement that causes a "segmentation fault", "bus error", or some other kind of fatal problem.
cerr << "at point 1\n"; //... cerr << "at point 2\n"; //... cerr << "at point 3\n";Finally, there can be more complex chunks of code intended for debugging only.
while(int i=0;i<n;i++) {cerr << a[i] << " ";}
In all of the above cases, it is often valuable to just disable the debugging statements rather than to permanently delete them. This can be done with the macros found in the file MmUtDebuggingMacros.h. Below is a re-write of the debugging code of the previous three examples using the macros in this file.
// ... all other include files should be above
#define DEBUG
#include "MmUtDebuggingMacros.h"
main() {
DBG_IAMHERE();
int a=6;
DBG_DISPLAY(a);
DBG(cerr << "a-4 is " << a-4 << "\n");
DBG_IAMHERE();
}
This piece of code produces the following output. The DBG_IAMHERE macro just prints the location of the code. The DBG_DISPLAY macro first prints out the name of the variable, then a colon and a space, and then the value of the variable. The DBG macro executes the code that is contained within (assuming DEBUG is defined, of course).
*** in file "test.cc" at line 5 *** a: 6 a-4 is 2 *** in file "test.cc" at line 9 ***To use the debugging macros, you include the file 'MmUtDebuggingMacros.h'. If DEBUG is defined by the time of inclusion of that file the DBG macros are active, otherwise they produce no output (they do not even cause any object code to be generated). It is generally a good idea to include all other file before the DEBUG macro is defined, in case any of the included header files themselves contain debugging macros that you may inadvertently activate.
It is also possible to define DEBUG globally with a '-DDEBUG' flag to your C++ compiler. However, the amount of debugging output generated by this can be overwhelming.
MmUtMathUtil.h could be called "mathematical functions that should be in LEDA but aren't, or that are in LEDA but don't work right". They compute determinants, greatest common divisors, and so on. Some that did not work in earlier versions of LEDA may have been already fixed in the version you use. We recommend that you first look if what you need is in LEDA. If it's there and works, use it. Otherwise, see if this file provides what you need.
MmUtDeallocator.h provides a template that you can use so that storage that you 'new' will automatically be deallocated when the allocating statement goes out of scope.[1] Consider the following code.
void someFunction(const string& type)
{
Base* b = generateClass(type);
...
delete b;
}
Now assume that you want to return from this function prematurely. With this code, you will have to delete b by hand. Worse, if an exception occurs, b will not be deallocated at all. The problem would be solved if you could write the following because b will be deallocated for you when the block is left.
void someFunction()
{
Base b;
...
}
However, you cannot do this in the first example because you do not know the exact class. The solution is to use the Deallocator class.
void someFunction(const string& type)
{
Deallocator<Base> d(generateClass(type));
Base& b = d.value();
...
}
MmUtCharBuffer.h does for character buffers what the MmUtDeallocator.h does for other classes. That is you no longer have to write:
char* buffer = new buffer[size]; functionThatExpectsAnAllocatedCharStar(buffer); delete [size] buffer;Instead you can write:
MmUtCharBuffer b(size); functionThatExpectsAnAllocatedCharStar(b);The second version is self-deallocating at the end of the block (and thus exception-safe). Another nice side-effect is that you have to specify the size of the buffer only once. The function can take b as the argument because there is a conversion operator to char*. Occasionally you run into a situation where the conversion may not work. In that case, use the value() function to convert to a char*.
fctThatOnlyTakesExactlyACharStar(b.value()); // do fctThatOnlyTakesExactlyACharStar((char*)b); // don't - casts invite troubleMmUtIStreamBuffer.h and MmUtOStreamBuffer.h provide conversions from C++ streams to character buffers and vice versa. Assume you have a character array a and want to parse its contents, but that your parser exclusively takes a stream as its input. The solution to the problem is below.
#include "MmUtIStreamBuffer.h"
main() {
...
MmUtIStreamBuffer b(a);
Parser p;
p.parse(b.stream());
...
Conversely, you may want to capture stream output in a character array. For example, assume your class c can print itself to a stream, but that you want to show that output in a scrolling window. Unfortunately, your toolkit can only handle character arrays. The solution is below.
#include "MmUtOStreamBuffer.h"
main() {
MmUtOStreamBuffer b(20000);
b.stream() << c << "\n";
b.nullTerminate();
functionThatExpectsACharStar(b);
fctThatOnlyTakesExactlyACharStar(b.value());
...
There are two caveats here. First, there is no protection against overflow of the buffer. The effects of such an event are undefined, but likely spectacular. Second, you have to remember to call nullTerminate() once all printing to the buffer is complete. Otherwise, whichever function later uses the buffer will happily continue reading until it encounters a completely unrelated ASCII null character in memory.[2]As you write your own C++ classes, you will probably want to give names to many of them (that is, have a string-valued attribute that holds the name of the class). MmUtNamedThing.h provides a superclass that you can publicly inherit from. This saves a few lines of code compared to giving your class a string-valued attribute that holds the name, defining access methods, defining modify methods, and so on.
MmUtVoidPtrListOps.h defines a bunch of low level operations on void-pointer lists these are intended for extensions of the template scheme in MmUtListTemplates.h; you will not need them unless you really want to get deep into writing your own collection templates.
MmUtParseUtil.h contains some functions that I often use within parsers, such as removing quotes from a string. You probably shouldn't; there likely is a more elegant way to write parsers, so that the functions in this file are not needed.
MmUtNCT.h and MmUtNCT.cc are convenience templates for you to use if you are making up a new class. To use them: (1) copy them to YourClass.h and YourClass.cc, respectively, (2) search-and-replace NCT with YourClass in the copied files using your favorite text editor, and (3) modify them to suit your needs. "NCT" stands for "New-Class Template". The files define things that most C++ classes typically have, such as copy constructors and assignment operators, compare functions, print functions and so on. The word "template" is used in its English sense; the files do not define C++ templates. No object code for the libMmUt.a library is generated from these files, of course; they are just text files.
MmUtListTemplatesAsSets.h provides macros that perform set-oriented functions on lists, such as intersection, union, difference, and so on.
SetTmpl.h is a wrapper around LEDA sets in the spirit of the list and dictionary templates discussed above. The problem is that LEDA sets do not keep the sets in any particular order. (Yes - I know sets are unordered by definition, but having different permutations of the same set printed makes it really confusing.) I now recommend that you use lists instead, in conjunction with the set functions for the list templates (see Section 4.3). IntSet.h is a pre-instantiated class for integer sets.
TimingDebug.h contains some macros that help with measuring how much time is spent in sections of the code. These have not been maintained for a while and probably will not work on your architecture. You should probably be using a 'profiler' utility program for that purpose anyway.
The idea of wassert.h was to provide a macro which prints the source file and line number for assertion failures without terminating the program (as the C++ standard assert.h does). However, this is probably a bad idea - there are really only two situations: (1) an internal error has occurred - you should be using the standard assert for that - or (2) an error has occurred because of erroneous user input - you should be printing a user-digestable message rather than a source code location.
You may want to use gcc for its wide availability. You want to use gcc-2.72 or higher with a compatible libg++-2.72 or higher. Available from prep.ai.mit.edu and its mirror sites.
If you do install your own version of gcc and there is a system-wide also make sure you are actually using the binaries, includes and libraries from your version.
If you do install your own version of make, ensure it is the one actually used by MasterMind!
On Solaris 2.5.1, compiling with g++ 2.7.2, we had to explicitly include the C++ include files of the compiler: -I/home/mm-proj/solaris/libg++-2.7.2-installed/lib/g++-include.
You really only need to make the libraries (everything in the src directory, but nothing in the prog directory)