Envision
A visual programming IDE for object-oriented languages
Adding a new node type to Envision's model

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.

Introduction

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.

IfStatement.h

Here is IfStatement.h :

/***********************************************************************************************************************
**
** Copyright (c) 2011, 2014 ETH Zurich
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
** following conditions are met:
**
** * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
** disclaimer.
** * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
** following disclaimer in the documentation and/or other materials provided with the distribution.
** * Neither the name of the ETH Zurich nor the names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
** INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
***********************************************************************************************************************/
#pragma once
#include "Statement.h"
#include "../expressions/Expression.h"
#include "../elements/StatementItemList.h"
namespace OOModel {
class IfStatement;
}
extern template class OOMODEL_API Model::TypedList<OOModel::IfStatement>;
namespace OOModel {
class OOMODEL_API IfStatement: public Super<Statement>
{
COMPOSITENODE_DECLARE_STANDARD_METHODS(IfStatement)
ATTRIBUTE(Expression, condition, setCondition)
ATTRIBUTE(StatementItemList, thenBranch, setThenBranch)
ATTRIBUTE(StatementItemList, elseBranch, setElseBranch)
public:
virtual bool findSymbols(std::unique_ptr<Model::ResolutionRequest> request) const override;
};
}
Definition: Reflect.h:35
Definition: TypedList.h:42
Definition: ContractFilter.h:35

Let's examine it starting from the top. The first noteworthy bit is:

namespace OOModel {
class IfStatement;
}
extern template class OOMODEL_API Model::TypedList<OOModel::IfStatement>;

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:

class OOMODEL_API IfStatement: public Super<Statement>

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:

  • Define a protected type called 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.
  • Import all constructors of the provided base type, so that your new node can call them.

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:

COMPOSITENODE_DECLARE_STANDARD_METHODS(IfStatement)

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:

ATTRIBUTE(Expression, condition, setCondition)
ATTRIBUTE(StatementItemList, thenBranch, setThenBranch)
ATTRIBUTE(StatementItemList, elseBranch, setElseBranch)

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:

virtual bool findSymbols(std::unique_ptr<Model::ResolutionRequest> request) const override;

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.

IfStatement.cpp

Here is IfStatement.cpp :

/***********************************************************************************************************************
**
** Copyright (c) 2011, 2014 ETH Zurich
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
** following conditions are met:
**
** * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
** disclaimer.
** * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
** following disclaimer in the documentation and/or other materials provided with the distribution.
** * Neither the name of the ETH Zurich nor the names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
** INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
***********************************************************************************************************************/
#include "IfStatement.h"
#include "ModelBase/src/nodes/TypedList.hpp"
#include "OOModel/src/typesystem/OOResolutionRequest.h"
namespace OOModel {
DEFINE_COMPOSITE_EMPTY_CONSTRUCTORS(IfStatement)
DEFINE_COMPOSITE_TYPE_REGISTRATION_METHODS(IfStatement)
DEFINE_ATTRIBUTE(IfStatement, condition, Expression, false, false, true)
DEFINE_ATTRIBUTE(IfStatement, thenBranch, StatementItemList, false, false, true)
DEFINE_ATTRIBUTE(IfStatement, elseBranch, StatementItemList, false, false, true)
bool IfStatement::findSymbols(std::unique_ptr<Model::ResolutionRequest> request) const
{
if (request->direction() == SEARCH_UP || request->direction() == SEARCH_UP_ORDERED)
{
auto ignore = childToSubnode(request->source());
Q_ASSERT(ignore);
bool found{};
if (condition() != ignore)
// Optimize the search by skipping the scope of the source, since we've already searched there
found = condition()->findSymbols(request->clone(SEARCH_HERE, false)) || found;
// Note that a StatementList (the branches) also implements findSymbols and locally declared variables will be
// found there.
if ((request->exhaustAllScopes() || !found) && parent())
found = parent()->findSymbols(request->clone(SEARCH_UP)) || found;
return found;
}
else return false;
}
}
The ModelBase plug-in defines the structure and basic functionality of the application tree in Envisi...
Definition: ChangeMonitor.h:31

All of the things here are just the corresponding definition macros. The only interesting bit is:

DEFINE_ATTRIBUTE(IfStatement, condition, Expression, false, false, true)
DEFINE_ATTRIBUTE(IfStatement, thenBranch, StatementItemList, false, false, true)
DEFINE_ATTRIBUTE(IfStatement, elseBranch, StatementItemList, false, false, true)

When defining the registered nodes you need to provide the following information:

  • The name of the class that you are defining
  • The name of the attribute you are registering
  • The name of the type of this attribute
  • A boolean flag which indicates if this node should be partially loaded by default. This should practically always be false as currently the partial loading implementation is not used.
  • Another boolean flag which indicates whether this attribute is optional or not. If you set this to 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.
  • A final flag that indicates whether this attribute should be saved to disk or whether it is transient and should only be used in memory. A value of true will make this attribute persistent. A value of false will make it transient.

allOOModelNodes.h

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:

#include "statements/IfStatement.h"