SYSTEM OVERVIEW
Configuration Parser (CFG) Subsystem
The CFG subsystem contains functions for parsing and formatting configuration files and populating configuration entities as defined in a DDF. The Configuration Parser makes use of a configuration DDF, in which the configuration entities have been previously defined. Using this DDF, the parser will allocate and load the configuration entities with data extracted from an external configuration file. After they are allocated, the entities are placed into a configuration model. The parser will support many common data types including integers, floating points and strings, as well as arrays and structures. The simple, C-like syntax of the configuration file allows the user to quickly create and modify any number of configuration entities. The Configuration Formatter likewise allows configuration entities defined in a specified configuration model to be formatted in a specified file.
GENERAL CONCEPTS
The Configuration Parser is driven by two separate, but related files: 1) The configuration DDF and 2) the configuration file. The configuration DDF defines the structure of each configuration entity. In other words, the configuration DDF defines what each entity "looks like", what fields are in each entity, and the names of these fields. This file is compiled to produce the necessary insert files for the application program. The configuration DDF may contain any number of entities, each of a unique name and type. The Configuration Parser supports many standard data types. These type tokens include:
int count double vstring struct
The Configuration Parser will also support integer, double, vstring and struct arrays (both fixed and variable). However, the parser will not support nested structures or arrays within structures. For example, the following entity will not be supported because it contains nested structures and arrays within a structure:
struct invalid_struct
{
int struct_array[4]; /* Invalid array within structure */
struct nested_struct /* Invalid nested structure */
{
...
};
};
Another limitation of the Configuration Parser is than it will only support single-dimensional arrays. A parsing error will occur if an array of more than one dimension is encountered. The following example will produce a parsing error:
int two_dim_array[2][3]; /* Invalid dimensioned array */
Please refer to the ddf_intro manual page for more information on the format and use of the DDF files. The second file, the configuration file, contains the data that will be loaded into the configuration entities. The Configuration Parser will allocate a configuration model, parse the specified configuration file, allocate the necessary configuration entities and load these entities with the data extracted from the file. When complete, a configuration model, complete with populated entities, will be available for the application program. The actual syntax of the configuration file is discussed below.
CONFIGURATION FILE SYNTAX
The configuration file is an ASCII file containing the parameter information to be parsed and loaded into the configuration entities. The general syntax of the configuration file is as follows:
/* Comment */ # Alternate Comment parameter = value; parameter = value1, value2, ...; parameter = "value"; parameter = "value1", "value2", ...;
where:
Two types of comment tokens are supported. Comments can be enclosed between the following characters /* comment */. A comment can also follow the # character. In this case, the comment is considered to be the entire physical line following the # character.
Comments can not be nested. The following are some examples of nested comments:
/* /* Nested Comment */ */
/* # Nested Comment */
# /* Nested Comment */
Blank lines may appear anywhere in the file
Parameter names must start with a letter and can contain any alpha-numeric character. Parameter names may also include (but may not start with) an underscore '_' character.
The parameter keyword may begin at any column in the file
If a value contains more than one token, each token must be delimited by a comma. Furthermore, all values must match the type of the field specified in the DDF.
If a value represents a text string (vstring value), it must be enclosed in quotes.
A text string cannot span multiple lines
A parameter name and its associated values may span more than one physical file line.
The list of parameter values must end with a semi-colon.
Parameter names must be unique within an entity
Only one entity of any type can be defined in the configuration file. In other words, each section header name must be unique.
SCALAR PARAMETERS
The Configuration Parser supports integer, double and vstring scalar values. In the configuration file, these parameters are defined as follows:
int_param = 1; double_param = 1.0; vstring_param = "This is a string token";
If a scalar value is optional, it may be defaulted by leaving off the values token. In this case, the field will assume the default value specified in the DDF. Some examples of defaulted values are as follows:
default_int = ; default_double = ; default_string = ;
As was stated above, the parameter and value pair may span more than one physical line. However, the entire text string token must reside on the same physical file line. In other words, the text string token cannot contain newline characters. The following are examples of parameter-values pairs spanning more than one line. Also included is an example of a string illegally spanning more than one line:
int_param
=
1
;
vstring_param =
"String token";
/* The following definition is not valid! */
illegal_vstring =
"Notice that this string value
spans more than one physical line"; # ILLEGAL!!
EXPONENTIAL NOTATION
The Configuration Parser also supports scientific-notation for double values. Valid exponent characters include D,E,e, and d. The exponent character is followed by an optional plus (+) or minus (-) sign along with the actual exponent value. Some examples are as follows:
d_val = 0.0e00; d_val = 10.12E+03; d_val = 3.14D2; d_val = 1.230000d-010; d_val = 10.e3 d_val = 0.000012345E100;
Notice that the plus (+) sign is really optional, but the minus (-) sign is necessary because, without it, the parser will assume a positive exponential value. Only floating point exponential notation is supported. Integer values defined in exponential notation will cause an error. The following definition will cause an error:
int_val = 10E+3; /* invalid integer exponential notation */
TYPECASTING
The value data type must match the expected data type of the field. For example, if the configuration field config_1 is declared to be of type int in the DDF, only integer values should be assigned to it in the configuration file. There are some cases, however, where casting of types can be performed. For example, if one tried to equate a double value to the integer type config_1, the double value would be cast to an integer. A warning message would be displayed that this typecast occurred. Other types of casting cannot be performed. For example, a vstring value can not be cast to an integer and an double can not be cast to a vstring value. In the case of illegal casts, the parameter will assume any specified default value. The following are examples of 'legal' and 'illegal' typecasts:
int_val = 1.678; /* Value cast to 1: message displayed */ int_val = "string"; /* Illegal cast: default value used */ d_val = 2; /* Value cast to 2.0: message displayed */ d_val = "string"; /* Illegal cast: default value used */ vstr_val = 100; /* Illegal cast: default value used */ vstr_val = 10.345; /* Illegal cast: default value used */
ARRAY PARAMETERS
The Configuration Parser supports integer, double, vstring and structure arrays. In the following example, assume an entity is defined in the DDF as follows (Structure types will be explained later):
entity config_example "Configuration example"
{
int ia[5] "Integer Array";
double da[5] "Double Array";
vstring sa[3] "String Array";
};
The configuration file would be of the form:
ia = 1, 2, 3, 4, 5; da = 3.14, 10.012E+3, .006d-7, 2.0, 100.0D-5; sa = "String #1", "String #2", "String #3";
An important characteristic of arrays is that not all of the array fields need to be defined. If too few data points are defined in the configuration file, the undefined index positions in the array are defaulted. In the above example, the array ia was 5-units long. If the configuration file only defined 3 data points, the last two data points would be defaulted in the configuration entity. On the other hand, if too many data points are defined in the configuration file, the extra data points are ignored. In this case, a warning message will be sent indicating that the extraneous data points were discarded. Any array data points that are not defined are defaulted. The following are examples of defaulted data, including instances in which too many data points are defined. For the following example, assume the same entity as above:
ia = 1, 2, 3; /* 4th and 5th positions defaulted */ ia = 1, , 3, 4, ; ia = , 2, 3, 4, 5; ia = , 2, 3, 4, ; ia = , , , , 5; ia = , , , , , ; ia = ; /* All array data defaulted */ ia = 1,2,3,4,5,6,7,8,9; /* Warning: extra data discarded! */
The same rules apply when defaulting double and vstring arrays. Notice, in the above examples, that two commas together will default a specific position in an array. Also, a leading comma will default the first position in the array. In the last example above, 9 data points were defined for an array 5-units long. In this case, the last 4 data points were ignored! A warning message would be sent, indicating that only five of the nine data points were extracted and assigned to the variable ia. It should be noted that the ENTIRE array can be defaulted in two ways. The first is to default each position by including just comma tokens. The second way is to simply use a semicolon with no comma or value tokens.
It is possible to redefine a variable within the context of the file. For example, if ia was defined once in the file and then, later on, was redefined, it will assume these latter values. It should be noted that the original contents of ia are COMPLETELY discarded. This has some important ramifications. The following example will demonstrate this:
ia = 1, 2, 3, 4, 5; /* All five values defined */
....
ia = 1, 2, 3;
The 4th and 5th data points, defined in the first instance, are discared when the second definition is encountered. According to the second definition, the 4th and 5th values are to be defaulted. They will be! This is to say that the original values at the 4th and 5th index positions will not be preserved. There are many other instances when redefinitions may discard previously defined values. For this reason, a warning message is displayed whenever a parameter is redefined within the context of a file.
ADDITIONAL ARRAY EXAMPLES
In the above examples, the arrays were defined to be of a fixed length as in:
int ia[5] "Fixed Integer Array"; double da[5] "Fixed Double Array"; vstring sa[3] "Fixed String Array";
However, the configuration parser will also support unsized arrays and arrays dimensioned by a count field (refer to the ddf_intro manual page for more information on these types of arrays). The following example shows the definition of an unsized array and an array dimensioned by a count field:
entity config_example "Another Configuration example"
{
int uia[] "Unsized Integer Array";
count num_da "Count field";
double dacf[num_da] "Double array with count field";
};
The number of elements in the unsized array is determined by the number of data points given in the configuration file. For the array with the count field, the number elements is based on the value assigned to the corresponding count field. The following are a few examples:
uia = 1, 2, 3, 4; /* Unsized array assigned 4 elements */ uia = , , , 4, ; /* Unsized array assigned 5 elements */ num_da = 5; /* Initializing the count field */ dacf = 1, 2, 3, 4, 5; /* Initializing the array of doubles */ num_da = 3; dacf = 1, 2, 3, 4, 5; /* Last two values will be discarded */ num_da = 6; dacf = 1, 2, 3; /* Last three values will be defaulted */
The next section describes the use of structures and arrays of structures. Like simple arrays, the configuration parser will support fixed and unsized arrays of structures.
STRUCTURES
The Configuration Parser also supports structures and arrays of structures. Each structure in the configuration file is delimited by an open and close brace '{ }' and each element within the structure is delimited by commas within these braces. As was mentioned above, the parser does not support nested structures or arrays within structures. Simply put, structures can only contain scalar integers, doubles and vstrings. Assume for the following examples that this entity is defined in the DDF:
entity config_example2 "Configuration Example #2"
{
struct c_str
{
int si "Structure int value";
double sd "Structure double value";
vstring ss "Structure vstring value";
};
};
The configuration definition would be of the form:
c_str = { 10, 10.0, "Structure string value" };
Notice how the complete structure is delimited by braces. The structure fields are defined within the braces, delimited by commas. As in simple arrays, the parser allows defaulting of any or all fields. Some examples of this capability are as follows:
c_str = { , 10.0, "string" }; /* Int value defaulted */
c_str = { 10, , "string" }; /* Double value defaulted */
c_str = { 10, 10.0, }; /* String value defaulted */
c_str = { 10, 10.0 }; /* String value defaulted */
c_str = { , , }; /* All values defaulted */
c_str = { }; /* All values defaulted */
c_str = ; /* All values defaulted */
The configuration parser does not, however, support syntax which may be familiar to C programmers for initializing a specific structure field:
c_str.si = 5; /* Not supported */ c_str.ss = "string"; /* Not supported */
Notice that there is more than one way to default all the values in the structure. In the case of arrays, if too many data points were defined, the extra data points were ignored. This is also the case with structure data. In the following example, too many structure data points are provided. These extra data points are ignored:
c_str = { 10, 10.0, "string", 4, 5.0, "extra" };
Only the first three data points are extracted. The extraneous data is discared. The Configuration Parser also supports arrays of structures. Now assume that the above entity contains an array of three structures. The data would be defined as follows:
c_str = { 1, 1.0, "one" }, { 2, 2.0, "two" }, { 3, 3.0, "three" };
Any data point could also be defaulted. Some examples:
c_str = ,{ 2, 2.0, "two" }; /* 1st and 3rd struct defaulted */
c_str = {,,,},{,,,},{,,,}; /* All structures defaulted */
c_str = { }, { }, { }; /* All structures defaulted */
c_str = , , , ; /* All structures defaulted */
c_str = ; /* All structures defaulted */
The Configuration Parser is very flexible in that any piece of data can be defaulted in any data type. However, caution must be used when generating the configuration file. If a data point is accidentally left out, it will be defaulted, possibly without the user knowing it!
The ways mentioned above to default values within arrays or structures do not all apply to the case of an unsizedarray of structures. For example, suppose the following unsized array of structures was defined in the DDF:
entity unsized_array_config_example "Unsized Array of Structures"
{
struct uns_str[]
{
int si "Structure int value";
double sd "Structure double value";
vstring ss "Structure vstring value";
};
};
When this type of array data is defined within the configuration file, the braces MUST be present for each element of the array. The configuration parser will not be able to determine where one structure element ends and the next element begins if the braces are not explicitly given in the configuration file line. The data points braceswithinthe can, however, be defaulted as was mentioned above. Some initialization examples are as follows:
uns_str = { }, { 2, 2.0, "two"}, { 3, 3.0 }, {,,,};
uns_str = { }, { }, { 3, 3.0, "three"};
uns_str = { 1, 1.0, "one" }, { 2 }, { };
The following initializations are invalid because the braces are not specifically given for each structure element in the array:
uns_str = , {2, 2.0, "two"};
uns_str = { 1, 1.0, "one" }, , {3, 3.0, "three"}
uns_str = , , ,;
uns_str = ;
SECTION HEADERS
The Configuration Parser will support any number of configuration entities. As this is the case, there must be some way to associate a given parameter with a specific configuration entity. To accomplish this, section headers are used to divide the configuration file into sections. In this scheme, parameters defined within a section are associated with a certain entity. For example, if the configuration DDF contained two entities, config_1 and config_2, the configuration file would be divided into two sections using section headers. The section header token is the name of the entity followed by a colon:
config_1:
/* Config_1 entity parameters defined here */
config_2:
/* Config_2 entity parameters defined here */
Notice, in the above example, the configuration file is broken down into two sections. All of the parameters defined after the config_1 header and before the config_2 header will be loaded into the entity config_1. Likewise, all of the parameters after the config_2 header will be associated with that entity. It should be pointed out that the section header name must be the same as the intended target entity. The section header is optional for a DDF with only one entity. But, if the DDF contains more than one entity, the section header is required! Without it, there is no way to determine which parameters go with which entity. A common mistake that is made is leaving off a section header in one part of the file. For example:
parameter = value; # <-- Missing section header!!
parameter2 = value2;
config_2:
parameter = value;
In the above example, the first two parameters are not associated with a section header. A parsing error will occur when this happens. As was mentioned above, section names must be unique. There is no way to define more than one of any type of entity. A file, for example, cannot contain two config_1 section headers. Finally, if a section header does not have a corresponding configuration entity in the DDF, a warning is displayed and the parameters in the section are discarded. For example, if the file contains the section header config_3 and there isn't a config_3 entity defined in the DDF, the parameters in this section are ignored and a warning message is displayed.
COMMAND LINE PARAMETERS
The Configuration Parser also allows the application to specify a command-line buffer. This command-line can contain configuration parameter data which will compliment or override the configuration file data. In other words, any configuration values in the command-line override the configuration values of the file. The benefits of this functionality quickly become obvious. Configuration parameters can be modified without actually having to edit the configuration file. Take, for example, two applications which use all of the same configuration parameter values, except one. With the command-line option, the application can set this single value, yet use the same configuration file for both applications.
It was discussed above that configuration parameters couldn't be redefined within the context of the file without causing a warning message. In the case of the command-line, however, parameter names can be redefined without warning messages. Any parameter values in the command-line will override the configuration file values.
An important, although somewhat obscure, topic is the use of section headers within the command-line. For a configuration DDF with only one entity, section headers are optional in the file. They are also optional in the command-line. The command-line, however, should not have a section header if the file does not! A simple way to visualize how the file and command-line interact is to image the command-line buffer appended to the end of the configuration file. In this case, however, redefinition of section headers is allowed. Therefore, the command-line can redefine section headers, or define new ones. Some examples of file and command-line interaction are as follows:
For a configuration file,
config_1:
value1 = 10;
config_2:
value2 = 0;
and a command-line buffer,
value 2 = 5;
config_1:
value 1 = 0;
one should imagine the command-line appended to the file. In this case, the field value2 of the command-line will override the value2 of the file, resulting in a final value of 5. Likewise, there will be a redefinition of the header section config_1 and, correspondingly, the field value1 will be reset to 0. A common error that occurs is demonstrated in the following example:
For a configuration file,
value1 = 1.0;
value2 = 2;
value3 = "string";
and a command-line buffer,
config_1:
value1 = 0.0
value3 = "Another string."
a parsing error will occur. When the file and command-line are appended, there are effectively two sections, but one section is missing its header. This is cause for an error. There are two ways to correct this problem, either take the header out of the command-line buffer, or add a section header config_1 to the configuration file.
PARSING A CONFIGURATION FILE
The configuration DDF should be compiled using the DDC compiler. This compiler will generate two insert files, whose names depend on the db_name used within the file: xxx_ddf.h xxx_structs.h
where xxx is the db_name used with the DDF file. Within this insert is the variable xxx_ddf, a pointer to the DDF information for the configuration DDF.
The CFG subsystem has one main routine, cfg_ParseFile. This routine takes a DDF pointer, file name and a destination model as its argument. The user can also specify a command-line buffer which will override the data extracted from the configuration file (see COMMAND-LINE-PARAMETERS above). This routine will allocate the destination model and set up all necessary model values. Therefore, it is not necessary to allocate the configuration model using mda_AllocateModel or set up the model defaults using mda_GetModelDefaults. All of this is performed internally in cfg_ParseFile. The function pdx_Initialize must be called before configuration parser is invoked. Once processing is complete, the function pdx_Terminate should be called. This routine will free all of the configuration entities and models. As this is the case, it is not necessary to call mda_FreeModel for configuration models.
To parse a configuration file and create a configuration model, the following code sequence is used:
#include "cfg_Interface.h"
#include " xxx_ddf.h " # <-- insert generated from ddc compiler
int model
char *cmd_line; /* Pointer to command-line or NULL */
char *file_name; /* Pointer to file name */
MDA_PTR db_ptr;
DDF_TF tf;
.
pdx_Initialize( PDX_NOOPT );
.
cfg_ParseFile( CFG_NOOPT, &xxx_ddf, file_name, cmd_line, &model )
.
/* Application Program */
.
pdx_Terminate( PDX_NOOPT );
The function cfg_ParseFile allocates the configuration model and returns this value in model. The configuration model contains all of the configuration entities defined in the configuration file specified by file_name and the command-line buffer cmd_line. If the application does not need to specify a command-line, NULL can be passed as this argument.
After the configuration model has been created, the application will need to obtain the database pointers of the configuration entities. The type:form number of the entity is used as the search key because each configuration entity is unique within a model. The function cfg_GetConfigEntity is used as follows to obtain the DB number of an entity:
.
.
cfg_GetConfigEntity( model, tf, &db_ptr );
The routine cfg_GetConfigEntity will return the MDA_PTR of the entity of type:form tf in the configuration model.
EXAMPLE USAGE
The following code example shows how to parse a sample configuration file. The DDF has been compiled and the insert config_ddf.h is present in the include search path. The sample code will parse the configuration file specified on the command line and dump the entity whose type:form number is equated to CONFIG_TEST
#include "cfg_Interface.h"
#include "config_ddf.h"
void main( int argc, char *argv[] )
{
int model;
char *cmd_line;
MDA_PTR db_ptr;
/* Validate command line parameters */
if( ( argc < 2 ) || ( argc > 3 ) )
{
/* Invalid command line: output message and quit */
printf( "Usage: parse <filename> <cmd line>" );
exit( 1 );
}
/* Initialize PDX subsystem */
pdx_Initialize( PDX_NOOPT );
/* Determine if command line parameter was supplied */
if( argc == 3 )
cmd_line = argv[2];
else
cmd_line = NULL;
/* Parse the configuration file and check for errors */
if( cfg_ParseFile(CFG_NOOPT,&config_ddf,argv[1],cmd_line,&model) != CFG_NOERR )
exit( 1 );
/* Find DB number of the entity CONFIG_TEST */
if( cfg_GetConfigEntity(model,CONFIG_TEST,&db_ptr) == CFG_NOERR )
{
/* Dump the entity CONFIG_TEST */
mda_DbgDumpEntity( MDA_NOOPT, db_ptr );
exit( 0 );
}
else
exit( 1 );
}
It should be noted that these entities are not placed in the cache when the are created. Given the MDA_PTR or any configuration entity, it is not necessary to lock it down using the function mda_LockEntity. Instead, the macro mda_GetLockedAddr should be used to obtain the configuration entity address.