1 Introduction
2 ALF: why and how?
-
Interpretive execution: ALF is directly interpreted and executed;
-
Compilative execution: ALF is translated into a UML model conforming to the fUML subset and executed on the actual target platform according to the semantics specified in the fUML specification;
-
Translational execution: ALF, as well as any surrounding UML concept in the model, is translated into an executable for a non-UML target platform and executed on it.
3 Advancing the state of the art and practice
3.1 Related work
3.2 Paper contributions
4 Translational execution of ALF
4.1 Type deduction
TypeDeduction
, in terms of Java objects to be naturally compliant to Xtend and thereby directly exploitable by the transformation process. We use TypeDeduction
for defining scopes (e.g. a method scope) and variables to them (e.g. a loop variable) and effectively deducing types during the translation of both structural and behavioural modelling elements. In Fig. 1, we depict a graphical representation of TypeDeduction
to help the reader to grasp the interdependencies among its constituents.
TypeDeduction
is the TypeScope
interface, which defines a set of methods for supporting type deduction. More specifically, TypeScope
makes it possible to create and search type scopes for storing and retrieving information about, for example, types returned by expressions, class types, variable definitions and types in the scope of a class or a namespace; specific APIs have been defined for this purpose. Through them, a realisation of TypeScope
can search in the tree-based type hierarchy until the specific variable or object is found. Through TypeScope
, it is also possible to derive the final type of a fully qualified name as well as of the tokens composing it. An example is given in Sect. 6.TypeScope
points always to its direct parent scope through the reference parent
; this is used for the scopes to reflect the structural hierarchy from namespaces down to methods and vice versa. TypeScope
is realised by TypeDeduction
’s main object, Namespace
, which corresponds to an ALF package or namespace. Namespace
can have a list of Import
interfaces, which correspond to ALF import elements. Import
extends TypeScope
and provides methods for searching the entire imported element scope. Additionally, Namespace
can own a list of Class
objects corresponding to ALF classes. In turn, Class
can have a set of MemberVariable
and MemberMethod
elements, corresponding to ALF class attributes and operations, respectively. MemberMethod
owns always an ALF_Block
object, which contains the unmodified body of the related method defined in ALF.TypeScope
is realised even by TypeDeductionScope
, which is exploited by Class
and MemberMethod
to manage their respective scopes and when deducing types within an ALF Block. TypeDeductionScope
builds the scope of a specific class or method in terms of the set of ALF syntax elements7 (ALF_SyntaxElement
) and their types (Type
).TypeScope
in terms of APIs can be summarised as follows:-
Scope hierarchy management: scopes are built following their hierarchical structure in the ALF blocks, and APIs are provided for creating, editing and retrieving this information. An example of such is the retrieval of the parent or child scope of the current scope;
-
Sub-scope management: sub-scopes associated with an ALF element can be created, overwritten and retrieved;
-
Name declarations check: already declared names can be sought within current scope, its parent scope and related imported namespaces;
-
Element type management: the type of an element (e.g. type associated with a variable name) can be added, overwritten and retrieved from the current scope, its parent/child scopes, as well as imported namespaces.
TypeDeduction
is completely transparent to the developer who simply runs the transformation process and obtains the automatically generated C++ code related to the design model. Moreover, TypeDeduction
does not represent an intermediate model or representation between ALF and C++, meaning that the possibility to back-propagate information provided by a compiler for, for example, model-level debugging at model level is not jeopardised.4.2 Memory management through smart pointers
-
Allocate basic typed variables on the stack and more complex objects on the heap. This solution would result in decent code performance, and it would be fairly easy to implement. The issue with it resides in the fact that it does not ensure prevention from stack overflows since allocations of basic typed variables may require more space than available on the call stack.
-
Allocate everything on the heap through smart pointers. This solution would give good code performance and would not be extremely difficult to implement. Moreover, it would prevent from stack overflows and memory leaks, since none of the objects are allocated on it.
-
Perform a smart allocation based, for example, on the scope of use and the size of the objects. On one hand, this solution would provide the best code performance and prevent from stack overflows. On the other hand, it would be very complex to implement and maintain since it would require (i) an analytical engine to determine object-specific allocations and (ii) a way to automatically generate destructors to handle memory deallocation of user-defined objects.
T
is wrapped as shared_ptr
< T
>, which represents a smart pointer to an object of type T
. When instantiating the object (i.e. class instance), the construct make_shared
<T
> is exploited to initialise the smart pointer referencing to the specific object and taking care of memory management.4.3 Translation of structure
TypeDeduction
for efficient type deduction when translating ALF behaviours represented by ALF blocks. More specifically, TypeDeduction
will be used to deduce types of variables and expressions for, for example, selection of the right member access operator and insertion of smart pointer constructs.TypeDeduction
similar to scenario 1) since it gives behaviours the context needed for the transformation to correctly translate them.4.4 Translation of behaviours
5 Mapping ALF concepts to C++
LinkOperationExpression
, used in ALF to create or destroy the links of an association, is not included since associations are not part of the minimum conformance. BitStringUnaryExpression
, used in ALF for unary operations on the type BitString, is not included since the type BitString is not conceived in C++. ClassExtentExpression
, used in ALF to obtain the objects in the extent of a class, is not included since it is not possible to search all instances of a particular class in C++. Sending instances of a signal in FeatureInvocationExpression
is not supported since signals are not part of the minimum conformance. Moreover, it provides the translation of a subset of ALF units, not included in the minimum conformance, for allowing the modeller to define an application using ALF only. In the following, we provide the technology-agnostic description of the supported mappings between ALF abstract syntax elements and corresponding C++ concepts reflecting the order in which ALF syntax elements are described in the official ALF specification (i.e. expressions, statements, units). Since exemplifying all the possible cases of use each syntax element is not possible (they are infinite), we aim at providing a set of representative examples for the reader to be able to reproduce the mappings with the transformation technology of her choice.5.1 Expressions
5.1.1 Qualified names
QualifiedName
is used to identify a UML named element, which may or not be a member of one or more namespaces. To avoid unpredictable C++ code, we did not cover the PotentiallyAmbiguousQualifiedName
concept. The remaining concepts are mapped as follows. A qualified name is constituted of non-empty set of bindings, either NameBinding
or PositionalTemplateBinding
(or a combination of the two). Bindings are separated by colons (‘::’), in case of ColonQualifiedName
, or dots (‘.’), in case of DotQualifiedName
.NameBinding
. More specifically, if the preceding NameBinding
represents a class object, colons and dots are mapped to C++’s arrow operator ‘->’ (Case 1, 2, 4); if it represents a property of primitive type, colons or dots are kept in C++ too (Case 3). Regarding PositionalTemplateBinding
, since we use smart pointers, the translation is done by wrapping the most internal NameBinding
in the shared_ptr
<T
> construct if the name represents a non-primitive type T
(Case 5).QualifiedName
, we navigates all the bindings composing it and identify the type of each of them. On one hand, in Case 2, type deduction identifies that classA
is declared in a parent scope as an instance object of class ClassA
; for this reason, the dot ‘.’ operator in ALF is replaced by the arrow ‘->’ operator in C++ (the same applies to Case 1, 4). On the other hand, in Case 3, property
is defined in a parent scope as integer, and thereby the dot ‘.’ operator is kept in the resulting C++. In Case 5, PositionalTemplateBinding
is done on ClassA
and ClassB
, which are both identified as class types in their parent scope by the deduction mechanism; the translation is done by wrapping them in the shared_ptr
\(<>\) construct (Table 1).QualifiedName
Case | ALF code | C++ code |
---|---|---|
1 | pkg::ClassA::property | pkg::ClassA->property |
2 | classA.property | classA->property |
3 | property.toString() | property.toString() |
4 | classA.op() | classA->op() |
5 | ClassA \({<}\)ClassB\({>}\)
| shared_ptr
\({<}\)pkg::ClassA\({<}\) shared_ptr
\({<}\)pkg::ClassB\({>}{>}{>}\)
|
5.1.2 Literal expressions (Primary expressions)
LiteralExpression
is composed of a single primitive literal. Since we aim at providing a translator which provides predictable C++ code, we did not account the primitive UnboundedValueLiteralExpression
since there is no standard way to translate it to a safe unbounded type in C++. Note that we do not forbid the use of unsupported ALF concepts at modelling level (e.g. through OCL constraints) since models are not only used for code generation purposes. Nevertheless, warnings are issued when generating code from models containing unsupported ALF concepts.BooleanLiteralExpression
, StringLiteralExpression
and NaturalLiteralExpression
, they have a natural corresponding in C++. For instance, a BooleanLiteralExpression
in ALF can either be represented by true or false values of a boolean (bool
) in C++.5.1.3 Name expressions (Primary expressions)
QualifiedName
. The mapping to C++ is given by the corresponding qualified name (see Sect. 5.1.1).5.1.4 ‘This’ expressions and Parenthesized expressions (Primary expressions)
ThisExpression
consists of the keyword this
, and it is translated to the same keyword in C++. ParenthesizedExpression
represents an expression contained by parentheses; parentheses are simply reproduced in C++, but the contained expression must be properly translated according depending on the expression type.5.1.5 Property access expressions (Primary expressions)
PropertyAccessExpression
is used to access the value of a property owned by the instance of a classifier. The expression is defined in terms of a feature reference, pointing to a target primary expression and to a name of a property of the type of the target primary expression. The translation is done by translating the primary expression according to its specific type and relating it to the name of the property to be accessed. In ALF, primary expression and names (NameBinding
) are separated by the dot ‘.’ operator, while in C++ this depends on the type of the object to be accessed; this is solved in the same way as for QualifiedName
(see Sect. 5.1.1).5.1.6 Invocation expressions (Primary expressions)
Tuple
, which represents the arguments for the parameters of the invocation. An invocation can be of the following types: BehaviorInvocationExpression
, FeatureInvocationExpression
and SuperInvocationExpression
. Tuple
and invocation types are described in the following four sections.5.1.7 Tuple
Tuple
is a list of expressions that describe the arguments for an invocation. They can be positional or named tuples; we provide a translation for positional tuples, since the current specification of C++ does not provide the concepts needed for representing named tuples. A Tuple
is translated by iterating on the list of expressions it represents, singularly translate each of them and concatenate their translation using the comma separator ‘,’. The concatenation is put within the parentheses of the translated InvocationExpression
. Single expressions are translated according to their type (Table 2).Tuple
ALF code | C++ code |
---|---|
(par1, “string”, new ClassA(), | (par1, “string”, make_shared <pkg::ClassA\({>}\)(), |
classA.op2(), {1,2}) | classA-\({>}\)op2(), {1,2}) |
5.1.8 Behaviour invocation expressions (Primary expressions)
QualifiedName
representing the behaviour to be invoked. The translation follows the same rules as the ones defined for QualifiedName
(see Sect. 5.1.1). Note that in order for a behaviour called from a model library to be correctly translated, a C++ library corresponding to the model library should be in place. In case C++ library and model library do not share the same naming convention, a wrapper (external to this code generator) should be provide to bridge the differences.5.1.9 Feature invocation expressions (Primary expressions)
PropertyAccessExpression
(see Sect. 5.1.5), and a final NameBinding
representing an operation call. In Case 1, an operation call on a property of primitive type keeps the same syntax in C++. In Case 2, an operation call on a class object is translated by separating the final NameBinding
and the accessed property by the arrow operator ‘->’. Case 3 and 4 represented cascaded feature invocation. In Case 3, the return value of op() is of primitive type; thereby, it is accessed by the final NameBinding
op2() through the dot operator ‘.’. In Case 4, the return value of op1() is a class object; thereby, it is accessed by the final NameBinding
op2() through the arrow operator ‘->’ (Table 3).FeatureInvocationExpression
Case | ALF code | C++ code |
---|---|---|
1 | property.op() | property.op() |
2 | classA.op() | classA->op() |
3 | classA.op().op2() | classA->op().op2() |
4 | classA.op1().op2() | classA->op1()->op2() |
5.1.10 Super invocation expressions (Primary expressions)
FeatureInvocationExpression
, but, instead of a feature reference, it has the keyword super
as target. The mapping is the same as for FeatureInvocationExpression
except for the keyword super
, which is instead substituted by the QualifiedName
of the superclass (Table 4).SuperInvocationExpression
ALF code | C++ code |
---|---|
super .op() | pkg::ClassA->op() |
5.1.11 Instance creation expressions (Primary expressions)
InstanceCreationExpression
represents the creation of a new instance of a class or data type. It consists of the keyword new
followed by a name (possibly qualified) representing the constructor method and a tuple representing eventual parameters for it. The new instance to be created is wrapped in a smart pointer through the construct make_shared
\({<}{>}\) (Case 1). In case the constructor is retrieved through a PositionalTemplateBinding
, the most internal NameBinding
is wrapped in a shared_ptr
<T
\({>}\) if it represents non-primitive type T
(Case 2). Through our type deduction mechanism, we identify the type of the instance to be created as a class type and wrap it in the make_shared
\({<}{>}\) construct for initialising a smart pointer for it (Table 5).InstanceCreationExpression
Case | ALF code | C++ code |
---|---|---|
1 | new ClassA(params) | make_shared
\({<}\)ClassA\({>}\)(params) |
2 | new ClassA\({<}\)ClassB\({>}\)() | make_shared <pkg::ClassA<shared_ptr <pkg::ClassB\({>}{>}{>}\)() |
5.1.12 Sequence construction expressions (Primary expressions)
SequenceConstructionExpression
groups values into a sequence of a specified type. It is represented by a list of expressions enclosed in curly braces and preceded by the specific type and the multiplicity brackets. A SequenceConstructionExpression
that begins with the keyword new
indicates an InstanceCreationExpression
for which a sequence of values is constructed too (Cases 2 and 3). In Case 1, we can see the construction of a sequence of integers and in Case 2 the construction a new array of strings. The mapping is pretty straightforward, except the fact that arrays are mapped to C++’s ‘vector
’. A more complex case is depicted in Case 3, where a sequence of class objects is constructed by directly creating a new object of the class as first element of the sequence. In this situation, the NameBinding
representing the sequence is wrapped into a shared_ptr
\({<}{>}\), while the NameBinding
representing the new class object is wrapped into a make_shared
\({<}{>}\).shared_ptr
\({<}{>}\) construct for leveraging smart pointers (Table 6).SequenceConstructionExpression
Case | ALF code | C++ code |
---|---|---|
1 | Integer[]{1, 2, 3} | vector <int\({>}\)({1, 2, 3}) |
2 | new String[]{“a”, “bc”, “df”} | vector
\({<}\)string\({>}\)({“a”, “bc”, “df”}) |
3 | ClassA[]{ new ClassA(), null} | vector <shared_ptr <pkg::ClassA\({>}{>}\){ |
make_shared <pkg::ClassA\({>}\)(), null} |
5.1.13 Sequence access expressions (Primary expressions)
SequenceAccessExpression
is exploited to retrieve the element in a specified position of a sequence. It is composed of two expressions, one identifying the sequence followed by one evaluating to an integer representing the index of the element to be retrieved and enclosed in brackets. The two expressions are transformed individually depending on the expression type. The structure of SequenceAccessExpression
coincides in ALF and C++.5.1.14 Increment and decrement expressions
FeatureLeftHandSide
) or a qualified name (NameLeftHandSide
), and an index expression. If the operand is represented by a feature reference, the translation is done according to what is defined for PropertyAccessExpression
(see Sect. 5.1.5), while, if represented by a qualified name, it is done as for QualifiedName
(see Sect. 5.1.1). The expression providing the index, if any, is translated depending on the expression type.5.1.15 Boolean unary expressions (Unary expressions)
BooleanUnaryExpression
is a unary expression composed of: an operand expression which evaluates to a boolean value and the negation operator ‘!’. Its translation is done by properly translating the expression representing the operand, according to the specific expression type, which is preceded by the negation operator ‘!’.5.1.16 Numeric unary expressions (Unary expressions)
NumericUnaryExpression
is a unary expression composed of: an operand expression which evaluates to a boolean value and a numeric operator ‘\(+\)’ or ‘−’. Its translation is done by properly translating the expression representing the operand, according to the specific expression type, which is preceded by the numeric operator.5.1.17 Cast expressions (Unary expressions)
CastExpression
is used to cast an operand expression to the type given by a QualifiedName
. The translation is done by translating the operand expression according to the specific expression type and the type according to the rules defined for QualifiedName
(see Sect. 5.1.1) (Case 1). In the specific case in which the type to cast to is defined as any
, the type is meant to be derived dynamically at runtime. In this case, any
is translated to auto_cast
(Case 2). Type deduction mechanisms support the translation in distinguishing the two cases. Since cast operations are not safe by definition, it is up to the modeller to ensure that the conversion is safe (Table 7).CastExpression
ALF code | C++ code |
---|---|
( any )classA.property | ( auto_cast )classA->property |
5.1.18 Binary expressions
ArithmeticExpression
is characterised by an arithmetic operator (\(+, -, *, /, \%\)). Note that arithmetic operator symbols as well as their associativity and precedence rules coincide in ALF and C++.ClassificationExpression
Case | ALF code | C++ code |
---|---|---|
1 | classA.classB instanceof ClassA | dynamic_cast <ClassA\(*{>}\)(classA->classB) \(!=0\)
|
2 | classA.classB hastype ClassA | typeid (ClassA) \(==\) typeid (classA->classB) |
AssignmentExpression
Case | ALF code | C++ code |
---|---|---|
1 | classA.classB \(=\) new ClassB() | classA->classB \(=\) make_shared <pkg::ClassB>() |
2 | classB \(=\) new ClassB() | classB \(=\) make_shared <pkg::ClassB>() |
3 | classB[i] \(=\) new ClassB() | classB[i−1] \(=\) make_shared <pkg::ClassB>() |
4 | i \(+=\) classA.prop | i \(+=\) classA->prop |
ShiftExpression
is characterised by a shift operator (\(<<\) signed left shift, \(>>\) signed right shift, \(>>>\) unsigned right shift). While signed left and signed right shift operator symbols as well as their associativity and precedence rules coincide in ALF and C++, unsigned right shift (\(>>>\)) is not available in the C++ specification; hence, we do not enforce its translation.RelationalExpression
is characterised by a relational operator (\(<,>, <=, >=\)). Relational operator symbols as well as their precedence rules in ALF and C++ coincide.ClassificationExpression
is a peculiar type of binary expression where, instead of the second operand expression, there is a QualifiedName
. ClassificationExpression
is used to check the result of the operand expression against a certain type represented by QualifiedName
. Operand and type are separated by a classification operator (instanceof
, hastype
). The operand expression is translated according to the expression type, while the type according to the mapping for QualifiedName
(see Sect. 5.1.1). Since the two operators do not have a direct correspondent in C++, we provide the following mappings. In the case of instanceof
, the expression checks whether the result of the operand expression has the same dynamic type of the given type represented by QualifiedName
or a direct or indirect subclass of it. In order to reproduce this behaviour, we dynamically cast the operand to a pointer representing the type we want to check the operand’s type with through the dynamic_cast
\(<>\) operator, and then we check that the result of the casting is not zero (Case 1). In the case the hastype
, the expression checks whether the result of the operand expression has the same dynamic type of the given type represented by QualifiedName
. In order to reproduce this behaviour, we extract the type identifiers of the operand and the given type using C++’s typeid
() function and compare them through the equality operator ‘\(==\)’ (Case 2) (Table 8).EqualityExpression
, LogicalExpression
and ConditionalLogicalExpression
coincide in ALF and C++.5.1.19 Conditional test expressions
ConditionalTestExpression
has three operand expressions. The first represents a boolean, and depending on its value, the expression selects either the second or the third operand as result. The mapping is done by translating the three operand expressions according to their expression type and separate them with the symbols ‘?’, between first (boolean) and second operand, and ‘:’ between second and third operand. Conditional test operator symbol ‘?’ and its associativity and precedence rules coincide in ALF and C++.5.1.20 Assignment expressions
AssignmentExpression
represents the assignment of a value represented by a right-hand side expression to a left-hand side which can be either a feature reference (FeatureLeftHandSide
) or a qualified name (NameLeftHandSide
) and can have an index expression. If the left-hand side is represented by a feature reference (Case 1), the translation is done according to what defined for PropertyAccessExpression
(see Sect. 5.1.5), while, if represented by a qualified name (Case 2), it is done as for QualifiedName
(see Sect. 5.1.1). The expression providing the index, if any (Case 3), is translated depending on the mapping rules for indexing (see Sect. 5.1.21). A simple assignment is done through the assignment operator ‘\(=\)’. A compound assignment compounds a binary operator with the assignment operator (Case 4) (Table 9).Case | ALF code | C++ code |
---|---|---|
1 | classA[2] | classA[1] |
2 | classA[i] | classA[i−1] |
LocalNameDeclaration
Case
|
ALF code
|
C++ code
|
---|---|---|
1 |
×
|
×
|
2 |
×
|
×
|
3 |
×
|
×
|
5.1.21 Indexing
-
if the index expression is represented by a numeric literal, then we subtract 1 to it (Case 1);
-
if the index expression is not a numerical literal, we translate the expression and concatenate ‘\(-1\)’ to it (Case 2).
IfStatement
ALF code
|
C++ code
|
---|---|
×
|
×
|
SwitchStatement
ALF code
|
C++ code
|
---|---|
×
|
×
|
5.2 Statements
5.2.1 In-line statements
InLineStatement
allows the modeller to embed code in a language other than ALF in an ALF block. No translation is needed in this case since in-line code, if defined in terms of the target language entailed by the transformation, is simply copied as it is in the output code. In our case, we provide support for C++ in-line code, while ignore in-line code defined in other languages.5.2.2 Block statements
BlockStatement
represents a block to be executed and can be seen as the container of statements sequence enclosed in curly braces. The concept of block is equally conceived in C++. Before translating a block, the type deduction mechanism creates a sub-scope representing the block’s scope within the current scope.5.2.3 Local name declaration statements
LocalNameDeclaration
is a statement which is employed for defining a local name together with its type and initialisation value. It is composed by a name declaration, which can include a multiplicity indicator, and an initialisation expression which can either initialise a sequence, a new instance or be another expression which evaluates to the type of the name to be declared. The syntax of the name declaration has two variants:-
‘
let
name :Type
’, inherited from UML (Case 1); -
‘
Type
name’, specific to ALF (Case 2).
Type
name’. If the initialisation expression defines the initialisation of a new sequence (Case 1), then the expression is translated according to the rules defined for SequenceCreationExpression
(see Sect. 5.1.12). In case it defines the initialisation of a new instance (Case 2), the translation follows the rules for InstanceCreationExpression
(see Sect. 5.1.11). For the other types of initialisation expressions, the translation is done by the rules defined for the specific expression type (e.g. ArithmeticExpression
in Case 3) (Table 11).5.2.4 Expression statements
Expression
followed by a semicolon, and it translated according to the rules defined for the specific expression type with a semicolon at the end.5.2.5 If statements
IfStatement
represents the conditional execution of a non-empty set of blocks. It is composed by an ordered set of sequential non-final clauses each of which having a condition in terms of a condition expression evaluating to a boolean and a body represented by a block. IfStatement
can have a final clause with a block to be executed in case none of the non-final clauses can be executed. Its translation to C++ is done by iterating on the non-final clauses in their order and for each of them transforming the condition expression according to the specific expression type and the statements sequence representing the block (each of the statements will be translated according to the specific type of statement). Sequential non-final clauses are concatenated through the ‘else
’ keywords both in ALF and in C++. The final clause is translated by translating the related block and concatenating it to the last non-final clause through the keyword ‘else
’. Before translating IfStatement
, the type deduction mechanism creates a sub-scope representing the IfStatement
block’s scope within the current scope (Table 12).ForStatement
Case
|
ALF code
|
C++ code
|
---|---|---|
1 |
×
|
×
|
2 |
×
|
×
|
3 |
×
|
×
|
5.2.6 Switch statements
SwitchStatement
executes one of a set of blocks depending on the value of an expression. The body of the SwitchStatement
is made of a list of clauses; each clause consists of a set of case labels and a block. Each case label contains an expression that must evaluate to a single value of a type conforming to the one of the switch expression. As for IfStatement
, a switch statement can have a final clause. Case labels are represented by expressions that are dynamically evaluated. In C++, case labels can only be represented by constant expressions; for this reason, we map SwitchStatement
to C++’s if-statement. More specifically, we iterate on the set of case labels, which are properly transformed into conditional expressions of if
and else if
; the final clause is translated into an else
without conditional expression. The clauses are translated as follows. An equality condition expression is created to resemble the switch’s cases. Note that multiple cases are translated by creating the related conditional expressions and then using them as operands for a conditional-OR (||) expression. The blocks representing case bodies are translated too. Before translating each clause, the type deduction mechanism creates a sub-scope representing the clause block’s scope within the current scope (Table 13).5.2.7 While and Do statements
WhileStatement
and DoStatement
are iteration loops which evaluate a condition expression (returning a boolean), and until it becomes false, it executes a block. The translation is done by transforming the condition expression according to its type and statements sequence representing the block. The structure of while-statement and do-statement in ALF and C++ coincide. Before translating WhileStatement
or DoStatement
, the type deduction mechanism creates a sub-scope representing the specific block’s scope within the current scope.5.2.8 For statements
ForStatement
iterates the execution of a block while assigning a loop variable to successive values of a sequence until it reaches the end of the sequence. The translation is done by transforming the loop variable according to its type and then transforming the block. When translating the loop variable, not all the cases have a direct translation to C++. The loop variable can be defined as a name label that assumes values within a sequence returned by an expression (Case 1); the loop variable can be declared explicitly (Case 2). These two cases are mapped to the C++’s range-based for-loop. More specifically, the translation to C++ is done by dynamically typing a reference variable named as the name label through the auto
. In these two cases, the loop variable is added to the scope of ForStatement
through the type deduction mechanism in order to enable its use within the block. Alternatively, the loop variable can be defined as a name label assuming values within a range of integer values with delimiting values represented by two expressions (Case 3). In this case, ForStatement
is mapped to a standard for-loop in C++, where the first expression defines the initial value of the loop variable, and the second expression represents the final value. Before translating ForStatement
, the type deduction mechanism creates a sub-scope representing the ForStatement
block’s scope within the current scope (Table 14).5.2.9 Break statements
BreakStatement
is represented by the keyword ‘break
’ followed by a semicolon, and it is used to stop the execution of an enclosing SwitchStatement
, ForStatement
, DoStatement
and WhileStatement
. The translation to C++ is straightforward since the same construct is used in C++ for the same purposes.5.2.10 Return statements
ReturnStatement
is used to determine that value and exit the operation. It is composed of the keyword ‘return
’, which is translated to the same keyword in C++, followed by an expression evaluating to the return value, which is translated according to the specific expression type.5.3 Units
5.3.1 Namespaces
NamespaceDefinition
defines the context for a set of owned members. It can be defined as either a package, through PackageDefinition
(see Sect. 5.3.2), or a classifier, through ClassifierDefinition
(see Sect. 5.3.3). The visibility of the name of an owned member outside the owner namespace’s scope is defined by a visibility indicator (‘public
’, ‘private
’, ‘protected
’) on the declaration of the owned member. Visibility indicator values coincide in ALF and C++ for properties and methods, but not for classes, which in C++ do not have any visibility indicator.5.3.2 Packages
PackageDefinition
is a type of namespace aiming at simply grouping owned members. In our solution, members owned by a package can only be classifiers of type Class
. The notion of package in ALF is mapped the C++’s namespace. The translation of PackageDefinition
is done by recreating the package structure, but using the keyword ‘namespace
’. Owned members are translated according to the mappings in the next section. Note that the translation of PackageDefinition
produces effects on both the C++ header and implementation files (Table 15).PackageDefinition
ALF code | C++ header file | C++ impl. file |
---|---|---|
package MyPackage{ | namespace MyPackage{ | namespace MyPackage{ |
} | } | } |
ClassDefinition
ALF code | C++ header file |
---|---|
public class ClassA{} | class ClassA;class ClassA{} |
PropertyDefinition
ALF code | C++ header file |
---|---|
private classB : ClassB[]; | private: vector <shared_ptr <ClassB\(>>\) classB; |
OperationDefinition
ALF code
|
C++ header file
|
C++ impl. file
|
---|---|---|
×
|
×
|
×
|
5.3.3 Classes (Classifiers)
ClassDefinition
represents a classifier whose instances are objects and defines a scope for owned properties and operations. The translation of ClassDefinition
is done by recreating the class declaration in C++, that is to say a stub declaration and a forward declaration of the class, and ignoring the visibility indicator. Owned members are translated according to the mappings in the next sections. In this case, the translation of ClassDefinition
only affects the C++ header file (Table 16).5.3.4 Properties (Features)
PropertyDefinition
is used to define a structural feature of a classifier; in our case, it is used to define attributes of a class. It is composed of a visibility indicator, a type in the form of a QualifiedName
, a name in the form of a string and eventually an initialiser expression. The translation is done by reproducing the visibility indicator, transforming the type according to the mapping for QualifiedName
(see Sect. 5.1.1) and transforming the initialiser expression, if any, according to the specific expression type. Type deduction is exploited to properly translate the QualifiedName
representing the property type; in the example below, the type of classB
is identified as an array of ClassB
objects which is translated to a vector
of smart pointers to ClassB
(wrapped in the shared_ptr
\({<}{>}\) construct). The translation of PropertyDefinition
only affects the C++ header file (Table 17).5.3.5 Operations (Features)
OperationDefinition
represents a behavioural feature of a class. We do not support abstract operations, nor redefinition or overloading. OperationDefinition
is composed of a visibility indicator, a name in the form of a string, a set of formal parameters with direction (we only support ‘in
’ direction), an eventual return parameter and a block. The translation is done by reproducing the visibility indicator and the operation name, transforming the parameters list (including eventual return parameter) and finally transforming the block according to the rules defined for BlockStatement
(see Sect. 5.2.2). The translation of parameters is done by considering ‘in
’ parameters and properly translating their type according to the rules defined for QualifiedName
(see Sect. 5.1.1). If the operation does not conceive a return parameter in ALF, the obligatory return parameter in C++ is set to ‘void
’. This is not the case of constructors, that is to say when OperationDefinition
is annotated with @Create; as in ALF, a return parameter is not expected in C++ either. Note that the translation of OperationDefinition
produces effects on both the C++ header (operation declaration) and implementation (operation implementation) files (Table 18).6 A running example: Self-orienting carrier robot system
-
Vector: used to define vectorial movements. It contains two properties, X and Y, defining 2-dimension coordinates, one constructor (Vector(..)), and three methods, vecRotateLeft(), vecRotateRight() and eq(..), which are exploited by the robot to perform movements;
-
Hitbox: used to describe sensitive spots such as pickup and drop-off areas as well as position and size of obstacles, and the robot’s body size. Positions are identified through pos of type Vector, while sizes through the properties height and width. The Hitbox(..) method represents the class constructor and the intersects With(..) method allows the robot to check whether the movement trajectory intersects an obstacle.
-
Robot: represents the main class and contains a number of properties and the methods used by the robot to carry out its mission. More specifically, Robot(..) is the constructor, fetch(..) and fetchList(..) are used to retrieve single items and the initial items list, respectively, while the remaining methods allow the robot to move and orient itself in the environment.
6.1 Translation of structure
TypeDeduction
would consist of a top Namespace
scope, called Robot, and representing the related ALF package (line 1 in Listing 2). The ALF classes Vector and Robot defined in the package are transformed into two Class
scope objects, Vector and Robot, which will be contained by the Namespace
. The Class
Vector scope will in turn contain two MemberVariable
objects, X and Y, corresponding to the properties defined in the Vector
ALF class. Moreover, it will contain a MemberMethod
object corresponding to the vecRotateLeft() operation. The Class
Robot scope will contain five MemberVariable
objects, left, forward, right, name, obstacles and one MemberMethod
object, Robot. The bodies of methods vecRotateLeft() and Robot(..) defined in ALF are stored as ALF_Block
objects as part of their corresponding MemberMethod
(i.e. vecRotateLeft() and Robot(..), respectively) scope, waiting to be transformed in the next transformation step. Type scopes are created in cascade following the hierarchical structure. For instance, Class
Vector scope will contain the types of the MemberVariable
objects, X and Y, and the scope of the MemberMethod
vecRotateLeft(). The type scope implementation of vecRotateLeft() will have a reference to the type scope of its parent, Vector.PackageDefinition
package
Robot which is mapped to the C++’s namespace
Robot. PackageDefinition
is translated by recreating the package structure, where owned members are translated according to their type. The translation of PackageDefinition
produces the same effect on both the C++ header and implementation (Listing 3) files.
ClassDefinition
public class
Vector and ClassDefinition
public class
Robot. They are translated to C++’s classes ignoring the visibility indicator. The translation of ClassDefinition
only affects the C++ header file (Listing 4).
ClassDefinition
object, its owned members are translated too. Let us consider two of the members owned by ClassDefinition
public class
Robot starting from PropertyDefinition
private
obstacles. The visibility indicator is reproduced as well as the property name. The type of obstacles is set to be an array of instances of class Hitbox (not shown in Listing 2). The type Hitbox is sought in the type deduction structure and found to be a class type, thus needing wrapping as smart pointer through the shared_ptr
\(<>\) construct. Moreover, since obstacles is an array, the smart pointer is in turn wrapped in the vector
\(<>\) construct. The translation of PropertyDefinition
only affects the C++ header file (Listing 5). Let us now consider OperationDefinition
public
Robot(..). The translation is done by reproducing the visibility indicator and the operation name and transforming the parameters list; the translation of the block is not part of the structural translation. Regarding translation of parameters, let us consider ‘in
’ obs. The type of obs is set to be an array of instances of class Hitbox. The type Hitbox is sought in the type deduction structure and found to be a class type and is thus wrapped in the shared_ptr
\(<>\) construct. Moreover, since obs is an array, the smart pointer is in turn wrapped in the vector
\(<>\) construct. Since OperationDefinition
Robot(..) is annotated with @Create, it is a constructor and thereby a return parameter is not expected in C++. The translation of OperationDefinition
Robot(..) produces effects on both the C++ header (Listing 6) and implementation (Listing 7) files.
In scenario 2, the structure of the system is defined in terms of a UML class diagram (see “Appendix A”). As aforementioned, in this scenario the transformation does not perform any structural translation, but only builds the type deduction structure reflecting the UML class diagram in order to enable the translation of behaviours. The type deduction structure which is generated recalls the one shown for scenario 1.
6.2 Translation of behaviours
OperationDefinition
representing the ALF unit for updateDirection(..) is translated similarly to what is shown in the previous section. The block representing the operation’s body is translated as follows. The first statement we encounter in Listing 8 (line 2) is LocalNameDeclaration
movePrio. The translation is done following the standard C++ form ‘Type
name’ (Listing 9). The type of movePrio is set to be an array of instances of class Vector. The type Vector is sought in the type deduction structure and found to be a class type and is thus wrapped in the shared_ptr
\(<>\) construct. Moreover, since movePrio is an array, the smart pointer is in turn wrapped in the vector
\(<>\) construct. The initialisation expression defines the construction of a sequence of objects of type Vector. Its translation is done by reconstructing the sequence in C++. The elements in the sequence are represented by ALF expressions of type InstanceCreationExpression
, that is to say new instances of type Vector. Since Vector is a class type, the transformation wraps it in the make_shared
\(<>\) construct for initialising a smart pointer for it. Parameters of InstanceCreationExpression
objects are represented by PropertyAccessExpression
elements. Let us consider this
.forward.X. Since this
represents an object of type Robot, the access to property forward is done in C++ through the arrow ‘->’ operator; since forward is of type Vector, even the access of property X is done through the arrow ‘->’ operator. The translation of the other parameters is achieved in the same way. Afterwards, we encounter a WhileStatement
(line 4 in Listing 8). Before translating WhileStatement
, the type deduction mechanism creates a sub-scope representing the WhileStatement
block’s scope within the current scope. The structure of a while-statement in C++ resembles the one in ALF; therefore, the transformation reproduces it. Then, it transforms the condition expression according to its type and the statements sequence representing the block (Listing 10). The condition expression is of type RelationalExpression
having two FeatureInvocationExpression
as operands and ‘<’ as relational binary operator. For translating RelationalExpression
, the transformation needs to translate the two operands, while the operator remains the same. Let us consider the first operand, FeatureInvocationExpression
this
.getDirection(movePrio[1], target). Since it represents an operation call on a class object (this
, of type Robot), it is translated by separating the final NameBinding
, getDirectionWeight(movePrio[1],target), and the accessed property, this
, by the arrow operator ‘->’. The translation of the second operand is done in the same way.
WhileStatement
, let us consider the first statement, being the ExpressionStatement
movePrio[1] = movePrio[2]; representing an AssignmentExpression
(line 5 in Listing 8). Both left- and right-hand sides are represented by indexed feature references. The interesting thing to notice here is the difference in indexing between ALF and C++. Since in ALF indexing starts from 1 while in C++ it starts from 0, and the index expression is represented by a numeric literal, then we subtract 1 to it; the resulting assignment in C++ is movePrio[0] = movePrio[1];. The next statement is a ForStatement
(line 9 in Listing 8). Before translating ForStatement
, the type deduction mechanism creates a sub-scope representing the ForStatement
block’s scope within the current scope. The loop variable is defined a name label (dir) that assumes values within a sequence represented by the QualifiedName
movePrio. The C++’s range-based for-loop is used in this case (Listing 11). More specifically, the transformation creates a reference to the variable loop (&dir) and type it as auto
(for dynamic typing). The variable is also added to sub-scope representing the ForStatement
block’s scope in order to enable its use in the block. At this point, the transformation translates the block of the ForStatement
. The first statement is represented by LocalNameDeclaration
direction (line 10 in Listing 8). The translation is done following the standard C++ form ‘Type
name’. The type of direction is set to be a primitive Integer
, thus corresponding to int
in C++. The initialisation expression defines a simple AssignmentExpression
to a NaturalLiteralExpression
(\(=\) 0), which is reproduced in C++. As part of the previous ForStatement
’s block, the transformation encounters an IfStatement
(line 11 in Listing 8). Before translating IfStatement
, the type deduction mechanism creates a sub-scope representing the IfStatement
block’s scope within the current scope. The structure of an if-statement in C++ resembles the one in ALF; therefore, the transformation reproduces it by properly translating its clauses (Listing 12). More specifically, the transformation iterates on the non-final clauses in their order, and for each of them, it transforms the condition expression according to the specific expression type and the statements sequence representing the block. Let us consider the first non-final clause if
(dir.Eq( this
.forward)). The condition expression is represented by a FeatureInvocationExpression
. Since it represents an operation call on a class object (dir, of type Vector), it is translated by separating the final NameBinding
, Eq( this
.forward)), and the accessed property dir by the arrow operator ‘->’. Then, the clause block is translated. The second non-final clause is translated in the same way. The final clause is transformed by translating the related block and concatenating it to the last non-final clause through the keyword ‘else
’. In the ForStatement
’s block, the transformation encounters a SwitchStatement
too. The translation is done by iterating on the set of case labels and transform them into condition expressions of ‘if
’ and ‘else if
’; the final clause is translated into an else
without condition expression (Listing 13). Before translating each clause, the type deduction mechanism creates a sub-scope representing the clause block’s scope within the current scope (as for the IfStatement
). The blocks representing case bodies are translated too. Let us consider the first (multiple) case label, ‘case
1: case
this
.setDirection(direction):’. The second part of the combined case label is represented by a non-constant expression; this is not allowed in the switch statement in C++, and that is the reason why we map SwitchStatement
to C++’s if-statement. The translation of the multiple case label is done by creating the related condition expressions and then using them as operands for a conditional-OR ‘||’ expression.
7 Validation
# ALF LoC | # Number runs | Avg. transform time |
---|---|---|
130 | 100 | 19.3 ms |
1015 | 100 | 99.5 ms |
10455 | 100 | 989.2 ms |
109335 | 100 | 10.9785 s |