The ceda front end allows arbitrary metadata to be recorded against reflected types. This is stored as part of the reflection information. Here is an example
$typedef+ uint32 Flags : [hex];
The metadata is recorded as a comma separated list of terms enclosed in square brackets. An example of a term is an identifier like hex.
The meaning and purpose of the metadata is application defined. In this case the metadata hex has been applied to the type Flags, presumably to control the appearance when values are printed by frameworks that use the reflection information.
As far as the C++ type system is concerned, Flags is just an alias for a uint32. However the reflection system allows it to have its own independently recorded reflection information.
The following example defines a type called Mass, that has kg units and the minimum is 0 meaning that negative masses are not permitted.
$typedef+ float64 Mass : [unit("kg"),min(0)]
A GUI framework can use this metadata to automatically display the units and to validate user entry (i.e. ensuring that the entered value isn’t negative). One of the aims in ceda is to promote GUI frameworks that can be used to automatically present and edit arbitrary data types. Meta-data on data types allows for fine-grained control over many details, including layout management, choice of edit control (e.g. slider versus text box), data validation etc.
In general metadata is applied with a colon followed by a list of metadata items in square brackets. The grammar is simple but extremely flexible. Here is a concocted example
$typedef+ float64 Pressure :
[
unit("kPa"),
range(50,300),
levels
(
[
[name("low"), max(75), action(email(supervisor))],
[name("medium"), range(75,100) ],
[name("high"), range(100,250)],
[name("fatal"), min(250), action( email(system_controller) )]
]
)
];
The meaning and purpose of metadata is not defined by the core ceda framework. It is simply a facility to be used (or abused) by application programmers as they see fit.
A functor consists of an identifier, optionally followed by a comma separated list of items enclosed in round brackets. Note that hex is a functor with arity zero and is equivalent to hex().
The grammar for the C++ type system is recursive. For example, given any type one can typically form arrays over that type. We therefore say that types can nest.
Metadata can be applied at all levels within a nested type. As an example, there are two places where we can apply metadata in the following
$typedef+ int32* Pointer;
In the following base(9) applies to the int32 and hex applies to the overall Pointer.
$typedef+ int32:[base(9)] *:[hex] Pointer;
Metadata can be applied to a function's return type, each of the argument types and also to the function as a whole. For example
$function+ float64:[unit("m/s")] GetVelocity(
float64:[unit("m")] s,
float64:[unit("m/s^2")] a )
: [description(
"Calculate velocity when a stationary object accelerates "
"at a for displacement s")]
{
return sqrt(2*a*s);
}
In practice metadata can easily obscure the code, and it is probably a good idea to tend to use typedefs where possible. This also avoids the need to repeat metadata information.
Metadata can be applied to an interface and also on each method in the interface. For example
$interface+ ICDPlayer : [releaseDate(2010,7,23)]
{
void Play(int32:[range(0,100)] speed);
};
Similarly metadata can be applied to a class/struct, and also to each reflected method or member variable.
The following EBNF grammar defines the format supported for meta-data.
boolean-literal = ’false’ | ’true’;
literal =
string-literal |
integer-literal |
float-literal |
boolean-literal;
element = list | functor | literal;
list = ’[’, [element, {’,’, element}], ’]’;
functor:
identifier,
[’(’, [element, { ’,’, element }], ’)’];
metadata = list;
where we assume the following
Symbol | Example | Description |
---|---|---|
identifier | _ax3_111 | Begins with letter or underscore, then any number of letters, underscores or digits. |
string-literal | "hello, world\n" | Double quoted string literal with support for escaped characters as for string literal in C/C++. |
float-literal | -10.453e-8 | Floating point number, as per C/C++ |
integer-literal | -78 | Base 10 integer literal |
The above typedef causes the following reflection information to be generated, to be compiled by a standard C++ compiler:
// Registration of Pressure
namespace
{
static const BYTE Pressure_type[] =
{
0xc3,0x81,0x00,0x00,0x44,0x01,0x00,0x82,0x02,0x00,0x42,0x32,0x00,0x00,0x00,0x42,
0x2c,0x01,0x00,0x00,0x81,0x03,0x00,0xc4,0xc3,0x81,0x04,0x00,0x44,0x05,0x00,0x81,
0x06,0x00,0x42,0x4b,0x00,0x00,0x00,0x81,0x07,0x00,0x81,0x08,0x00,0x80,0x09,0x00,
0xc2,0x81,0x04,0x00,0x44,0x0a,0x00,0x82,0x02,0x00,0x42,0x4b,0x00,0x00,0x00,0x42,
0x64,0x00,0x00,0x00,0xc2,0x81,0x04,0x00,0x44,0x0b,0x00,0x82,0x02,0x00,0x42,0x64,
0x00,0x00,0x00,0x42,0xfa,0x00,0x00,0x00,0xc3,0x81,0x04,0x00,0x44,0x0c,0x00,0x81,
0x0d,0x00,0x42,0xfa,0x00,0x00,0x00,0x81,0x07,0x00,0x81,0x08,0x00,0x80,0x09,0x00,
0x0d
};
static const char* Pressure_stringTable[] =
{
"unit",
"kPa",
"range",
"levels",
"name",
"low",
"max",
"action",
"email",
"supervisor",
"medium",
"high",
"fatal",
"min",
};
const ceda::ReflectedTypedef Pressure_typedef =
{
"Pressure",
Pressure_type,
Pressure_stringTable
};
struct SelfRegisterPressure
{
SelfRegisterPressure()
{
cxVerify(ceda::gfnRegisterReflectedTypedef(&Pressure_typedef) == ceda::NSE_OK);
}
} srPressure;
}
All the strings have been placed into a string table, and the type definition contains all the meta-data using an array of bytes. The latter is referred to as a byte code. The byte code uses a very small memory footprint yet can be processed (i.e. decoded) quickly and efficiently.