This chapter has three sections the first two describe the code generated for SpecTcl and Root. The last section describes how to build and wrap unpackers for SpecTcl and Root so that you can use the same code with both.
All generators are expected to generate their definitions and code in a namespace. By default, this namepace is derived from the basename of the output file by removing leading path elements. This default namespace can be overidden using the namespace directive in the specificationfile.
In our example, we've used the default namespace.
In this section we'll look at the code generated for the SpecTcl target. If you're not curious feel free to skip this section and the next as you don't really need to know the nitty gritty details of the generated code to write an unpacker.
First let's look at what a struct is. For SpecTcl, a struct becomes
a C++ struct. In addition to the data members, each struct has an
Initialize
method which takes, as a parameter
a base name for the members of the struct. The generated code for
a struct's Initialize
method initializes
all value and array members and invokes the Initialize
method of each struct and each element of a structarray.
SpecTcl naming conventions are used for structarray intializer calls so that each element has a numeric parameter name part. All this may be somewhat confusing so let's look at some examples.
Example 4-1. Initialization of simple struct members for SpecTcl - the declaration
struct Ta { value a value b low=-1.5 high=1.5 units=cm }
This struct generates the following bit of code in the header:
Example 4-2. Initialization of simple struct members for SpecTcl - the header
namespace spec { /** Data Structure definitions **/ struct Ta { CTreeParameter a; CTreeParameter b; void Initialize(const char* basename); }; ... }
The first thing to note is that the structs are generated in a namespace. The namespace is derived from the basename of the output file. You should, therefore, use the same output file basename for all targets (pointing each target to a different directory e.g.).
Next note that each member generates a tree parameter. The
Initialize
takes a basename
parameter. This will be either the name of the instance if this
structure is instantated, or the path to the member if this struct is
a member of another struct that's instantiated. More on that later.
The Initialize
implementation generated is:
Example 4-3. Initialization of simple struct members for SpecTcl - the implementation
void spec::Ta::Initialize(const char* basename) { std::string name(basename); a.Initialize(name + '.' + "a", 100, 0, 100, ""); b.Initialize(name + '.' + "b", 100, -1.5, 1.5, "cm"); }
The two points to see are how the basename has a period and the member
name grafted on to each call to the member's own
Initialize
member and how the metadata
for b
are used to parameterize that
variable's Initialize
call. The
intermediate code does not provide a distinction between provided
and not provided metadata, instead it defaults missing metadata as
SpecTcl itself does if metadata is not proviced. That's where the
parameterization of a
's
Initialize
method comes from.
Let's look at a more complex example:
Example 4-4. SpecTcl complex struct initialization - definition file:
struct Tb { array a[10] array b[20] } struct Tc { struct Tb a structarray Tb b[10] value c units=megawidgets array d[100] low = 0 high = 4095 bins=4096.65 units=channels; }
This generates a pair of structs. Tb
has a pair of CTreeParameterArray
members.
Tc
contains a struct an array of structs (both
Tb
) a value and an array. This generates
the header file segment:
Example 4-5. SpecTcl complex struct initialization - header file
namespace spec { ... struct Tb { CTreeParameterArray a; CTreeParameterArray b; void Initialize(const char* basename); }; struct Tc { struct Tb a; struct Tb b[10]; CTreeParameter c; CTreeParameterArray d; void Initialize(const char* basename); }; ... }
Note how both structs have initialization methods defined. Let's see
the code generated for the two Initialize
methods:
Example 4-6. SpecTcl complex struct initialization - C++ file
void spec::Tb::Initialize(const char* basename) { std::string name(basename); a.Initialize(name + '.' + "a", 0, 100, 100, "", 10, 0); b.Initialize(name + '.' + "b", 0, 100, 100, "", 20, 0); } void spec::Tc::Initialize(const char* basename) { std::string name(basename); a.Initialize((name + '.' + "a").c_str()); for (int i = 0; i < 10; i++) { char index[3]; sprintf(index, "%02d", i); std::string elname = name + "." + "b." + index; b[i].Initialize(elname.c_str()); } c.Initialize(name + '.' + "c", 100, 0, 100, "megawidgets"); d.Initialize(name + '.' + "d", 0, 4095, 4096, "channels", 100, 0); }
Note how the Tb
just initializes the tree parameter
arrays. Tc
, on the other hand initializes each of its
elements and a loop is generated to create new parameter base names like
arrays for the structarray element and to initialize each struct in that
structarray individually.
Finally, SpecTcl does not require any code for
SetupEvent
as SpecTcl itself invalidates the
underlying parameters very efficiently (an O(1) algorithm). Similarly
CommitEvent
requires no code from SpecTcl.
Initializee
, however must get the initialization
ball rolling by initializing all instances. Those instances in turn,
if they are structs or structarrays will initialize their members.
Here are the instance declarations:
Example 4-7. Initialization code for SpecTcl - definition file
value b low=-100.5 high=100 bins=200 units=cm value a array c[20] array d[5] units=furlongs structinstance Ta stuff structinstance Tc mystuff structarrayinstance Tb morestuff[20]
This generates the following in the header:
Example 4-8. Initialization code for SpecTcl - header
namespaces spec { ... #ifndef IMPLEMENTATION_MODULE extern CTreeParameter b; extern CTreeParameter a; extern CTreeParameterArray c; extern CTreeParameterArray d; extern struct Ta stuff; extern struct Tc mystuff; extern struct Tb morestuff[20]; #endif /** API functions callable by the user **/ void Initialize(); void SetupEvent(); void CommitEvent(); }
Note the #ifdef that brackets the instance declarations. This is because the implementation file will actually define those objects and C++ considers it an error to both declare an object as externa dn define it locally. Thus the C++ module will have a #define IMPLEMENTATION_MODULE prior to including the generated header.
The header also declares the three API functions.
Example 4-9. Initialization code for SpecTcl - C++ file
namespace spec { CTreeParameter b; CTreeParameter a; CTreeParameterArray c; CTreeParameterArray d; struct Ta stuff; struct Tc mystuff; struct Tb morestuff[20]; } ... void spec::SetupEvent() {} void spec::CommitEvent() {} void spec::Initialize() { spec::b.Initialize("b", 200, -100.5, 100, "cm"); spec::a.Initialize("a", 100, 0, 100, ""); spec::c.Initialize("c", 100, 0, 100, "", 20, 0); spec::d.Initialize("d", 100, 0, 100, "furlongs", 5, 0); spec::stuff.Initialize("stuff"); spec::mystuff.Initialize("mystuff"); for (int i = 0; i < 20; i++) { char index[3]; sprintf(index, "%02d", i); std::string elname = std::string("morestuff.") + index; spec::morestuff[i].Initialize(elname.c_str()); } }
This should be about what you'd expect to see for initialization by now.