Envision
A visual programming IDE for object-oriented languages
|
In this tutorial we will see how to add a new node type in Envision.
For this we will use the OOModel::IfStatement node as an example.
Imagine you had an OO language that has if statements, and support for this feature was missing in Envision. You want to extend Envision's OO model with a new node type representing if statements. Luckily, this is very easy to do if you take advantage of some convenience classes provided by Envision.
To create a new node all you need to do is create a new class that inherits from Model::Node. It is possible to inherit directly from it, but then you'll have more work to do. The recommended approach, that we'll demonstrate below, is to inherit from Model::CompositeNode (either directly or indirectly). Model::CompositeNode provides a whole infrastructure for declaring nodes that makes it really easy to define new node types.
Here is IfStatement.h
:
Let's examine it starting from the top. The first noteworthy bit is:
In order to be able to put a node into a list of nodes, it is necessary to explicitly instantiate the Model::TypedList template. This is necessary to support Windows as it can't properly handle templates with static fields otherwise. With this first part in the .h file we prevent implicit instantiation. Then the class is explicitly instantiated in the
.cpp file.
Next comes the class declaration:
The node declaration should use the plug-in's API macro and should inherit directly or indirectly from Node. When specifying the base node type you must do so through the Super
template. This is required by the underlying framework. The Super
template does two things:
Super
which represents the base class. This makes it possible to simply use Super
anywhere where you need to refer to the base class. This is especially helpful when the base class is a long template instantiation. Inheritance via Super
is used very often throughout Envision.In this case we inherit from OOModel::Statement, which itself inherits from OOModel::StatementItem, which finally inherits from Model::CompositeNode. Both OOModel::Statement and OOModel::StatementItem are just marker classes that serve only as a common base, grouping other classes together. For the purposes of this tutorial they are not interesting. We really only care about the fact that OOModel::IfStatement ultimately inherits from Model::CompositeNode.
Next we notice the first line of the class declaration:
This macro needs to appear as the very first line of the declaration of any node that inherits from Model::CompositeNode. There is a similar (also mandatory) macro for nodes which inherit directly from Node - NODE_DECLARE_STANDARD_METHODS
. Each of these macros needs to be complemented with a couple of macros in the .cpp file:
For COMPOSITENODE_DECLARE_STANDARD_METHODS:
DEFINE_COMPOSITE_EMPTY_CONSTRUCTORS
COMPOSITEDEFINE_NODE_TYPE_REGISTRATION_METHODS
For NODE_DECLARE_STANDARD_METHODS:
DEFINE_NODE_EMPTY_CONSTRUCTORS
DEFINE_NODE_TYPE_REGISTRATION_METHODS
Together these macros save us a ton of writing. They declare many methods for the new node type and setup required data structures. At some point there was an attempt to translate their functionality to templates but the result was more verbose and less flexible so we stuck with the macros.
Next we get to the most interesting bit of the node declaration:
With the help of the various ATTRIBUTE
macros you can easily define what nodes your new composite node will consist of. Here you see the simplest way of using the macro. You only need to specify the type of node, its name (which will also be the name of the getter function for this child), and the name of a setter function. Each of these macro declarations need to have complementing definitions in the .cpp file which we'll see later.
Finally we see a method declaration:
This is an advanced function for the OOModel::IfStatement node. For simple nodes you do not need to define this function. The only reason we need it here, is because it is possible to declare a variable inside the condition of an if statement and we need a way to find this variable when resolving references. We won't go into details here as name resolution is a big topic on its own.
This is it really. The essential thing for any Model::CompositeNode derivative is to declare what attribute nodes it consists of. The rest of the code is just boilerplate.
Here is IfStatement.cpp
:
All of the things here are just the corresponding definition macros. The only interesting bit is:
When defining the registered nodes you need to provide the following information:
false
as currently the partial loading implementation is not used.false
, the attribute is mandatory and calling the getter will always return a valid value. If you do not explicitly set the attribute, a default constructed value will be used. If you set this flag to true
, the attribute is optional and calling the getter might return nullptr
if the attribute has not been set.true
will make this attribute persistent. A value of false
will make it transient.This last step is really just for convenience and is not directly related to creating a new node type.
It is a good idea to provide an easy way for others to use all of the nodes you create. Typically this means creating a header file which simply includes all of the headers files of the nodes you create. In the case of the OOModel plug-in this file is called allOOModelNodes.h
. If you are creating a new node within the OOModel plug-in please make sure to add its header to allOOModelNodes.h: