The following examples are given in assumption that we develop a template to be used with the UML Data Processor. All functions implemented by UML Processor are enlisted in the "uml_externals.tl2" file. The use of TEAL generators in the application different from DbUML a data-specific processor has to be developed (see "Host application development").
5.1 Basic work flow
Any template must contain at least one function available for external (from the host application) call. There are only two available signatures of exported functions supported by TEAL - with one parameter of object type (single-object) and two parameters of object type (double-object). Usually a single-object function is enough to cover any requirement for the generation process. To export such functions, use "pragma export" and "pragma export2" directives correspondently. The host application can specify which of the exported functions should be used for generation. If it doesn't, then the first exported function is used.
So, the simplest template can be implemented like
// define and declare function
func test(obj theObj) begin
"Hello, world!"
endfunc
// export function
export test;
This simple template doesn't use the input parameter at all. Let's make it more useful - show the name and the metatype of the input name. To access this information, we need the help of a data processor. We need to include the file declaring data processor functions into our template project to make these functions available -
// include file containing declaration of name() and metatype() functions
include "uml_externals.tl2"
func test(obj theObj) begin
"Input object " theObj.name " is of " theObj.metatype " type"
endfunc
export test;
Usually, the object parameter passed to the generator represents a top element in an object hierarchy. In the case of UML-based generation, it can be a UML package. Now let's investigate the contents of the package
include "uml_externals.tl2"
func test(obj theObj) begin
if theObj.metatype = "package" then
"Input object " theObj.name " is an UML package%cr%"
// iterate through list of classes in the package
for c in theObj.classes begin
" class " c.name "%cr%"
endfor
else
"Input object " theObj.name " is of " theObj.metatype " type"
endif
endfunc
export test;
In addition to classes, UML packages can contain sub-packages. To show a full hierarchy, we need to use recursive function calls -
include "uml_externals.tl2"
// define recursive function
func showPackage(obj pkg) begin
theObj.name " is an UML package%cr%"
// iterate through list of classes in the package
for c in theObj.classes begin
" class " c.name "%cr%"
endfor
// iterate through list of sub-packages
for p pkg.packages begin
showPackage(p)
endfor
endfunc
func test(obj theObj) begin
if theObj.metatype = "package" then
showPackage(theObj)
else
"Input object " theObj.name " is of " theObj.metatype " type"
endif
endfunc
export test;
Similarly, we can show information about class structures, including attributes, methods and association roles.
5.2 Macro usage
Usage of functions allows to avoid a repetitive block of code and reduces both the final program size and the possibility of errors. Functions can be parameterized with data to be more flexible and reusable. But sometimes it is desirable to parameterize function not with data, but with a piece of code. Well, while it is impossible with a function, we can use macros. LetТs consider the following example of the template code for Pascal class generation
"T"cls.name" = class "
private
"
for a cls.attributes begin
" F" .name ": " .type ";
"
end for
for r cls.roles begin
" F" .name ": " .type ";
"
end for
...
"
protected
"
for a cls.attributes begin
" function Get" .name "(): " .type ";
procedure Set" .name "(const value: " .type ");
"
end for
for r cls.roles begin
" function Get" .name "(): " .type ";
procedure Set" .name "(const value: " .type ");
"
end for
...
"
public
"
for a cls.attributes begin
" property " .name "(): " .type " read Get" .name " write Set" .name ";
"
endfor
for r cls.roles begin
" property " .name "(): " .type " read Get" .name " write Set" .name ";
"
endfor
...
The same attribute and role iterations are repeated again and again and again... but each time with a different code to generate. We can easily hide the redundant code using the macro
macro forEachAttrRole (theClass, theCode) begin
for a theClass.attributes begin theCode endfor
for r theClass.roles begin theCode endfor
endmacro
"T"cls.name" = class "
private
"
forEachAttrRole(cls, " F" .name ": " .type ";%cr%")
...
"
protected
"
forEachAttrRole(cls,
" function Get" .name "(): " .type ";
procedure Set" .name "(const value: " .type ");
" )
...
"
public
"
forEachAttrRole(cls, " property " .name "(): " .type " read Get" .name " write Set" .name ";%cr%" )
...
That's it! The template code is less crowded and more readable now.
Unfortunately, like in many other programming languages (for example C/C++), using macros is sometimes rather complicated and limited. First of all, you have to remember - parameters in macro call are not TEAL elements (like in the case of functions), but they are actual template codes which will replace the formal parameters in the macro definition. Second, you can easily pass a macro parameter which will create a non-compilable code -
macro List(param1, param2) begin
for p param1 begin param2 endfor
endmacro
...
List( pkg.packages, "package " p.name ) // will work, but you rely on usage of "c" iterator inside of macro
List( pkg.classes, "class " c.name ) // error, iterator "c" won't exists inside of macro code
It is better to modify the macro in order to have everything under control -
macro List(param1, param2, param3) begin
for param1 param2 begin param3 endfor
endmacro
...
List( p, pkg.packages, "package " p.name ) // ok
List( c, pkg.classes, "class " c.name ) // ok
Also, you cannot use commas inside of actual macro parameters; instead, use the %comma% constant expression - it will be replaced by the comma character inside of the macro.
5.3 Advanced work flows
5.3.1 Short-coded expression usage
Boolean expressions are used in the WHERE clause inside of the FOR or IF commands. For example, if we want to enlist attributes of simple types (Strings, Integers and Floats), and also want to put non-derived ones first, then we would use something like
for a cls.attributes where (a.type=String or a.type=Integer or a.type=Float) and a.isderived=false begin
a.name "%cr%
endfor
for a cls.attributes where (a.type=String or a.type=Integer or a.type=Float) and a.isderived=true begin
a.name "%cr%
endfor
First of all, Boolean expressions can be very long, and hence difficult to read. Secondly, but may be even more importantly, we repeat the same function calls on the same objects - in our example two times - it reduces the performance of the text generation. Fortunately, TEAL has a solution to address both issues - short-coded Boolean expressions. The idea is simple - some object properties are associated with special codes (usually one character long). For example we can associate the code "D" with derived attributes and code "S" with attributes of the simple type. Now we are able to use the expression "+S-D" which returns TRUE only if the attribute is simple and non-derived -
for a cls.attributes where +S-D begin
a.name "%cr%
endfor
and the expression "+SD" which returns TRUE only if the attribute is both of the simple type and derived -
for a cls.attributes where +SD begin
a.name "%cr%
endfor
The code is much shorter and readable now. Plus, the TEAL generator calculates short codes only once - when short-a coded expression is applied to objects and keeps them cached until the generation process is completed. That is not all - and internal representation of short codes is a bitmap which makes execution of short-coded expressions extremely fast.
Of course, short codes have their limitations - first, they cannot represent OR operation--only AND ones with optional inversion -
[not] value1 AND [not] value2 and [not] value3 ...
but you will still be able to combine them as a part of more complex expressions -
(+S-D or .type=boolean") and .stereotype="PK"
Second, short-coded expressions work only with a "current" object, i.e. object represented by the iterator in the innermost FOR statement.
Also, you have to memorize all code associations, so it makes sense to short-code only frequently used object properties; don't try to associate sort codes with all possible aspects of objects. If a set of codes is limited, it will not take a long time to get used to their meanings.
The next question is, how can the actual short-code associations be done? As everything else in TEAL: using a function. The function has to be declared with two object parameters and return all short codes for the object passed as the first parameter. The function should handle all supported types of objects; to identify object type use the UML processor function "metatype()".
func ShortCodeAssoc(obj object, obj object2) begin
...
if object.metatype="class" then
if object.stereotype="abstract" begin "A" endif
if object.stereotype="aggregated" begin "G" endif
if object.stereotype="enumeration" begin "E" endif
...
endif
if object.metatype="attribute" then
if object.type="String"
or object.type="Integer"
or object.type="Float" begin "S" endif
if object.isderived="true" begin "D" endif
...
endif
...
endfunc
The TEAL compiler has to know what function it should use for short code calculations. It is done using the "pragma CodeCalc" directive. For our example
pragma CodeCalc ShortCodeAssoc
One step is still missing. Recall that short codes are stored internally as a bitmaps. To make an association between codes and bit positions, the TEAL compiler needs calibration text - a comma-delimited list of all possible codes used in your template (more correctly, all possible codes returned by your short-code calculation function). It is done by calling the same function with NULL as a first parameter. So the function should handle this case too -
func ShortCodeAssoc(obj object) begin
if object.metatype="" begin "A,G,E,S,D,..." endif
...
endfunc
You can check an example of a short-code calculation function in file "uml_filter.tl2".
<begin of obsolete text>
If you take an intent look at a short-code calculation function, one question should arise - what is the second parameter is used for? Well, it is possible to parameterize the short-code calculation with an additional object parameter. This is not a very usable functionality, but still deserves some discussion. Suppose you want to associate an "M" code with the fact that an attribute has mapping information. The UML processor supports a function "map(object, container)" which returns mapping information for a class or an attribute. And the function requires two parameters - the object itself and the object, in the context of which the mapping is requested. The second parameter is very important - if you have a class descendant from a parent one, it can be mapped to a different table; its attributes would be mapped differently also. But in the UML model, these attributes are represented only one - in the parent class. So if we have the attribute itself, we are not sure what mapping we are looking for until we specify is it in child of the parent class context. So, to make such functions usable inside the short-code calculation function, we need an additional parameter. By default, its value is NULL. But we can explicitly specify it using extended short-code syntax -
for a cls.attributes where +SM@cls begin ... endfor
The code after @ - "at" character is an iterator code for the second parameter object.
The use of the second parameter makes a situation more complicated than nessesary and should be avoided - in other words, a short-code calculation function should use only single-parameter functions. If such functions should be used in a Boolean expressions, use them explicitly Ц
for a cls.attributes where +S and a.map(cls)<>"" begin ... endfor
<end of obsolete text>
5.3.2 Internal functions development
Internal functions extend the functionality of the TEAL generator. You can implement any number of functions and use them inside of TEAL templates. To make it possible, a function has to
- be implemented in C++ language;
- be of the void type;
- have first parameter of reference to the std::string type;
- optionally have any number of additional parameters of the std::string type;
The first parameter references a result string produced by the generator. The function is supposed to add a result to this string. A header file is not required. The file has to be compiled using the same C++ compiler which will be used by TEAL to compile the template. The resulting object file can be added to a library or used as is. The object or library file has to be used in linking of the template (see "C++ compiler configuration"). Finally, the function has to be declared as internal using TEAL syntax.
An example -
// definition in C++ file
void substr(std::string& result, std::string text, std::string start, std::string length) {
result += text.substr(atoi(start.c_str()), atoi(length.c_str()));
}
...
// declaration in TEAL file
internal substr(text, start, length);
TEAL compiler comes with a set of standard internal functions. All of them are listed in "teal_internals.tl2", implemented in "teal_internals.cpp" and included in "generator.lib" files. You can use them as examples.