Click to download a .zip file containing the command-line executable, example outputs and user guide.
Features
If you're an experienced C++ programmer, the following ritual will be very familiar to you: creating .h file; declaring the class; adding a header guard; declaring methods; adding private members; adding getters and setters to access those private members; creating a .cpp file; defining methods; and so on and so forth. The goal of the ClassGen language and compiler is to automate the monotonous aspects of the class creation process as much as possible, freeing the programmer to focus on more important design and implementation details.
The current features of ClassGen include:
- Multiple class declarations per ClassGen script. Each class has separate .h and .cpp files generated for it.
- Copyright and author comments at the tops of all files.
- Descriptive comments at the top of .h files.
- Descriptive comments immediately preceding the class declaration.
- Appropriately named header guards.
- #include statements in the .h file. Supports both "file" and <file> syntaxes.
- Single base class inheritance.
- Constructors, destructors, methods and fields. Default constructors are always generated.
- Hierarchical namespaces (e.g. Engine::Graphics::Sprite).
- Access levels (public/protected/private). Access levels are supported both for inheritance and class members.
- Descriptive comments immediately preceding a field declaration in the .h file.
- Descriptive comments immediately preceding a constructor, destructor or method definition in the .cpp file.
- Assigning a default value to a member field in the default constructor.
- Assigning an argument's value to a member field in a user-defined constructor.
- Constant, pointer and reference types.
- Generating inline Getter and/or Setter functions for a field.
Worked examples
1 2 |
class MyClass { } |
1 2 3 4 5 6 7 8 9 |
#ifndef MY_CLASS_H_ #define MY_CLASS_H_ class MyClass { public: MyClass(); }; #endif // MY_CLASS_H_ |
1 2 3 4 5 |
#include "my_class.h"
MyClass::MyClass() {
}
|
A public default constructor was automatically added to the class declaration, with an accompanying stub in the body file (if I continue development of ClassGen I will likely remove this or make it optional).
A more fully featured script would be something along the lines of:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
copyright "My Company Name 2014" author "Alice Brown" %% The GameObject class should be used for anything that the player can interact %% with in the world. GameObjects handle collision detection, among other things. include "texture.h", "sprite.h", "collision_mask.h" include <cstdio> // Since GameObjects are interactable they should be visible, so this class inherits // from Sprite. GameObjects are guaranteed a unique ID and may also be given a name. class Engine::Game::GameObject : Graphics::Sprite { field char* m_name{NULL} with getter, setter // Runs collision detection and response against all other objects in the world. method void CheckForCollisions(World& world) // @returns true if this object is colliding with the given one. private method bool IsColliding(GameObject& object) field Game::CollisionMask m_collision_mask with setter field bool m_dead with getter destructor // Loads in the texture and sets up the object, including ID. // @returns true if successful, false if an error occurred. method bool Initialize(const char* texture_name) // If this is -1 then it means that the object hasn't been // properly initialized yet. protected field int m_id{-1} with getter } include "renderable.h", "color.h", <cstdio> // A set of characters which can be rendered to the screen. class Graphics::Text : protected Graphics::Renderable { field char* m_text{NULL} with setter field Graphics::Color m_color{255, 255, 255, 255} with setter constructor(char* text : m_text, Graphics::Color& color : m_color) destructor // Renders the text to the given window. method void Render(Graphics::Window& window) } |
The first class member is a field with a pointer type, default value, getter and setter. The second is a method with a comment and an argument with a reference type. The third member is another method, this one with a specified access level. Following on are more field declarations (with a namespaced type) and a destructor. Finally there are two more members; a method with multiple comments and a constant argument type; and lastly another field, with multiple comments and a specified access level.
The second class declaration has a specified access level for the inheritance and a more complex default value for one of the fields. It also demonstrates a constructor with arguments which will be used to initialize specified fields.
Processing this script with ClassGen produces 4 files: game_object.h, game_object.cpp, text.h and text.cpp. These are shown below. game_object.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
// (c) My Company Name 2014 // Author: Alice Brown // The GameObject class should be used for anything that the player can interact // with in the world. GameObjects handle collision detection, among other things. #ifndef GAME_OBJECT_H_ #define GAME_OBJECT_H_ #include "texture.h" #include "sprite.h" #include "collision_mask.h" #include <cstdio> namespace Engine { namespace Game { // Since GameObjects are interactable they should be visible, so this class inherits // from Sprite. GameObjects are guaranteed a unique ID and may also be given a name. class GameObject : public Graphics::Sprite { public: GameObject(); ~GameObject(); void CheckForCollisions(World& world); bool Initialize(const char* texture_name); inline char* GetName() const; inline bool IsDead() const; inline int GetId() const; inline void SetName(char* value); inline void SetCollisionMask(Game::CollisionMask value); protected: // If this is -1 then it means that the object hasn't been // properly initialized yet. int m_id; private: bool IsColliding(GameObject& object); char* m_name; Game::CollisionMask m_collision_mask; bool m_dead; }; inline char* GameObject::GetName() const { return m_name; } inline bool GameObject::IsDead() const { return m_dead; } inline int GameObject::GetId() const { return m_id; } inline void GameObject::SetName(char* value) { m_name = value; } inline void GameObject::SetCollisionMask(Game::CollisionMask value) { m_collision_mask = value; } } // namespace Game } // namespace Engine #endif // GAME_OBJECT_H_ |
Within the class declaration are the members, but they are not in the order they were found in the script. Members are sorted first by access level (public first, then protected, then private), and within an access level they follow the order of constructors, destructors, methods, getters, setters and finally fields. The order of members relative to each other is preserved; for example the method CheckForCollisions was declared before the method Initialize in the script and so it comes first in the set of public method declarations.
Starting with the public members: a default constructor has automatically been added along with the destructor declared in the script; then come the public methods and finally the generated getter and setter functions. The names of these getters and setters are based on the fields they relate to; for example the field m_name had the functions GetName and SetName generated for it. ClassGen supports starting member names with m_ and ignores it when naming getters and setters. The getter for the boolean member m_dead has been named IsDead, rather than GetDead.
The protected members consist of a single field with multiple accompanying comments. The private members comprise a private method followed by the remaining field declarations, again matching the relative order they were declared in in the input script.
With the class declaration complete, the definitions of the getter and setter functions come next. These match the order they were declared in. Finally the file ends with closing braces for the namespaces and a closing #endif for the header guard.
game_object.cpp contains:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// (c) My Company Name 2014 // Author: Alice Brown #include "game_object.h" Engine::Game::GameObject::GameObject() : m_id(-1), m_name(NULL) { } Engine::Game::GameObject::~GameObject() { } // Runs collision detection and response against all other objects in the world. void Engine::Game::GameObject::CheckForCollisions(World& world) { } // Loads in the texture and sets up the object, including ID. // @returns true if successful, false if an error occurred. bool Engine::Game::GameObject::Initialize(const char* texture_name) { } // @returns true if this object is colliding with the given one. bool Engine::Game::GameObject::IsColliding(GameObject& object) { } |
text.h is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
// (c) My Company Name 2014 // Author: Alice Brown #ifndef TEXT_H_ #define TEXT_H_ #include "renderable.h" #include "color.h" #include <cstdio> namespace Graphics { // A set of characters which can be rendered to the screen. class Text : protected Graphics::Renderable { public: Text(); Text(char* text, Graphics::Color& color); ~Text(); void Render(Graphics::Window& window); inline void SetText(char* value); inline void SetColor(Graphics::Color value); private: char* m_text; Graphics::Color m_color; }; inline void Text::SetText(char* value) { m_text = value; } inline void Text::SetColor(Graphics::Color value) { m_color = value; } } // namespace Graphics #endif // TEXT_H_ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
// (c) My Company Name 2014 // Author: Alice Brown #ifndef TEXT_H_ #define TEXT_H_ #include "renderable.h" #include "color.h" #include <cstdio> namespace Graphics { // A set of characters which can be rendered to the screen. class Text : protected Graphics::Renderable { public: Text(); Text(char* text, Graphics::Color& color); ~Text(); void Render(Graphics::Window& window); inline void SetText(char* value); inline void SetColor(Graphics::Color value); private: char* m_text; Graphics::Color m_color; }; inline void Text::SetText(char* value) { m_text = value; } inline void Text::SetColor(Graphics::Color value) { m_color = value; } } // namespace Graphics #endif // TEXT_H_ |
Improvements
Other features such as automatically deleting and nullifying pointer members in a class' destructor, being able to #include files in the body file, and using namespaces would also improve the program.
But by far the biggest limitation of ClassGen as it stands is coding style. The program only supports one coding style, and it's one I myself already have problems with (getters and setters after the class declaration?). If I were to continue development of ClassGen, the first thing I would add would be a style sheet that can be passed as an optional parameter to the executable and which would dictate the coding style of the generated artifacts. This could include:
- The tab size to use, and whether to use tabs or spaces.
- The directory to save generated artifacts to.
- The file extensions to use (.h/.hh/.hpp/.hxx/.cc/.cpp/.cxx and so on).
- Where to define inline getters and setters.
- The naming convention to use for files, getters and setters.
- What style of comments to use (this could even be extended to include support for third party tools such as Doxygen).
- The placement of opening and closing braces, brackets and parentheses.
- The order of access levels in the class declaration, and whether to have separate access levels for methods and fields.
- The order that member types appear within an access level.
- A maximum line length.
- Etc.