Introduction
The purpose of program make_cpp_class.py is to make it easier to produce a C++ class header file and implementation file. This program will often produce correct code, however, the program cannot always identify the proper header files to include, and therefore the generated code might have to be edited before the code can be successfully compiled. This program saves a lot of typing.
The program is for experienced C++ programmers, because understanding the changes that must be made to the generated code require understanding the C++ language.
The program takes file as input and a class name and a few optional switch arguments. The input file contains code in a very simple language. Once code is generated using this program, the input file can be discarded.
The body of any method in the input file is copied to the generated code without any changes. In other words, you still must write the code that does anything, the program merely eliminates the need to write the C++ class boilerplate code.
A folder is created with the same name as the class name passed on the command line and the generated code is created in this folder.
Program Files
- make_cpp_class.py - The main program file that parses the arguments and calls the code generator.
- bclass.py - The main code generation module that parses the input file and creates the generated files.
- bparse.py - Parses the input file.
- file_buffer.py - Buffers the input file data; used by the bparse.py
- function_info.py - Stores method information, including the method name, return type, and argument types.
- include_file_manager.py - Stored the include file names for some standard types.
- type_and_name_info.py - Stores a name, either of a variable or a method and a type_info instance.
- type_info.py - Stores attributes of a type.
- test_input.txt - A file that can be used as input to the program to demonstrate some capabilities. This file is not part of the program.
Using the code
The program takes an input file name, a class name, and a few optional switch arguments as input. The input file contains code in a very simple language. Once code is generated using this program, the input file can be discarded.
The body of any method in the input file is copied to the generated code without any changes. In other words, you still must write the code that does anything, the program merely eliminates the boilerplate that must normally be written when producing a C++ class.
While the input language is described below, looking at file 'test_input.txt' will make the descriptions below much clearer.
The program does relatively little lexical analysis of the data in the input file. While some sequences will be recognized as erroneous, if the input file is not correct the program might produce garbage. This is another reason it's important to know C++ if you use this program.
Usage:
python make_cpp_class.py <class_name> <input_file_name> [-b base_class_name] [-a author_name] [-f] [-t]
The program accepts the following switches:
-b base_class_name, --base_class base_class_name - Inherit from the named base class.
-a author_name, --author author_name - The author name.
-f, --full - Write full detailed header information.
-t, --abstract - Make all virtual methods be abstract methods.
-h, --help - Show help and exit
The Input File format
Data Member Format In The Input File
Data members are first in the file and are declared using the following format:
<Type> <Name> [= initial_value]<;>
The type can be a pointer parameter, a reference parameter, and be preceded by const, volatile, or static. 'Const volatile", while valid, are not allowed together. I didn't implement this only to simplify the parser; if needed use one keyword and add the other to the generated code.
A sample data member set might be:
short m_age;
int m_count = 7;
Foo_t * m_foo;
static const float m_height = 1.0;
If an initial value is supplied for a data member, then everything between the equal sign and the terminating semicolon is used as the initial value in the generated code.
Static data members generate the appropriate code in the header file and implementation file.
Constructors and the Destructor
After the data member declarations, the constructors, destructor, and methods are listed in the input file. Headers are written automatically for all methods.
The return value for a method can be declared the same as for a data member type and also allows the additional keywords 'inline' and 'virtual'. These cannot be used together and both should be first on the function declaration line.
Because the class name is specified on the command line, when used for a constructor or destructor, the class name is specified in the input file using the character '@'.
Here are some examples that show two constructors and one virtual destructor.
@()
{
}
@(int x)
{
}
virtual ~@()
{
}
Member initialization lists are created for every constructor. Member initialization lists might need to be edited, but much of the time what is written can be left as-is.
If the class name passed on the command line is 'Foobar' and the data member list is the example shown above, then the first constructor definition shown above, which starts with the '@' character, would produce the following generated code:
Foobar::Foobar()
: m_age(0),
, m_count(7)
, m_foo(NULL)
{
}
The Copy Constructor and the Equals Operator
Putting the keyword, "copy:" on an input line will automatically generate code for a copy constructor and code for the equals operator. Both of these methods will call a generated 'Copy' method that does a shallow copy of the class data members.
copy:
Putting the keyword "nocopy:" on an input line will generate a 'private' copy constructor and equals operator, both without any implementation, thus instances of the class cannot be copied.
nocopy:
Using both "copy:" and "nocopy:" generates an error message.
Methods
Methods are declared similar to a C function, but the methods can be made const by putting the 'const' keyword at the end. Also, "= 0" can be added to the end to make the method an abstract method. If "= 0" is used, then no method body will be generated in the class implementation file. Here are three method examples that could be specified in the input file:
double accumulate(double addend)
{
m_sum += addend;
return m_sum;
}
const & Foobar GetFoobar() const
{
return m_foobar;
}
virtual int doSomething(int anIntegerToUseForSomething) const
{
}
A method can also be declared using the 'static' keyword.
Property Methods
There is another special keyword to define properties. A data member for a property should not be declared in the data member section mentioned above.
The syntax to generate a property, which uses the ":property" keyword is:
:property <type> <data_member_name> <property_name>
This defintion will create a method named set<property_name> and a method named get<property_name>.
Here is an example property definition.
property: int m_age Age
The property definition above will result in the code generator writing the following data member and methods. The code headers are omitted here for brevity. Also, the data member m_age would be declared in the data member section of the class header file.
int m_age;
int Age() const
{
return m_age;
}
void setAge(int the_value)
{
m_age = the_value;
}
If the data type used in the declaration of a property is not an intrinsic type, then the code generator will generate slightly different code than shown above. Here is an example:
property: Person_t * m_person Person
Person_t * m_person;
Person_t Person() const
{
return m_person;
}
void setPerson(const Person_t * the_value)
{
m_person = the_value;
}
The Message Body
The parser detects the start of a method body by parsing past the method signature to the first open curly bracket character, or '{'.
At that point, ignoring brackets that are contained in string declarations, an open curly bracket causes a counter that is initially at zero to be incremented, and close curly brackets '}' causes the counter to be decremented. When the count returns to zero, the method body is ended.
If the brackets are not correct in the input file, the program will produce incorrect code.
The Mistakes This Program Makes
As already mentioned, there is minimal lexical analysis of the input file, so garbage in will result in garbage out.
Also, the program makes the assumption that all type names that designate either non-intrinsic (non-built-in) types or are that are not one of the special type names that are handled in the include-file-manager code, will generate an 'include' statement to include a header file with the same names as the type name followed by the extension ".h". Of course, this often won't be a valid header file name, and therefore it will often be necessary to either delete the include-statement or change the name of the header file in the include statement.
Also, any data types in the body of the code are not detected, and it might be necessary to include header files in the generated code for these types.
Tips and Tricks
If I have some data that I know the program will not generate, such as a comment-block, or 'include statements', and I want these in the class header file, I will declare an inline function, such as:
inline void dummy()
{
#include "foobar.h"
#include "barfoo.h"
}
Later, I edit the file, move the text where it belongs, and delete the unneeded method.
Also, using '@' for the class name allows generating multiple classes with the
same set of methods that are all derived from the same base-class by running the program multiple times passing a different class name and the same base class on the input line.
Points of Interest
The program was written out of a desire to eliminate the necessity to code all the redudant information in a C++ header file and C++ implementation file, and also to write all the boilerplate code that is needed for every class.
This program could be better, but it would have taken many months, if not years, to write a proper program that handled all cases with no errors. For me, this would have been slower than just writin this tool and then fixing the small errors in include files.
Still, if thousands of people are going to generate code, it might be worthwhile to create an input language grammar, and use yacc, or ANTLR to create a parser, and then add a code generator. This hypothetical program could handle parsing message body and would be more robust.
History
Initial posting