Adding Support for Connector Abstractions in the UniCon Compiler

Gregory Zelesnik
School of Computer Science
Carnegie Mellon University
Pittsburgh, Pennsylvania 15213-3890


Abstract

Addition of support for connector abstractions to the UniCon compiler is non-trivial, yet the process is remarkably similar for each connector. To understand, define, and automate the process of adding support for new connectors, the expertise for the current set of connectors was grouped together and localized to a few well-defined places within the compiler. The expertise for each individual connector was then extracted from these locations and placed in a database, organized by individual connector. A tool was then developed to automatically generate versions of the UniCon compiler containing any configuration of connectors. This tool reads a specification of the connectors to be included and obtains the expertise for these connectors from the database. This paper describes how to populate the connector expertise database with the support required for a new connector abstraction.


This research was supported by the Carnegie Mellon University School of Computer Science and Software Engineering Institute (which is sponsored by the U.S. Department of Defense), by a grant from Siemens Corporate Research, and by the Wright Laboratory, Aeronautical Systems Center, Air Force Materiel Command, USAF, and the Advanced Research Projects Agency (ARPA) under grant F33615-93-1-1330. The U.S. Goverment is authorized to reproduce and distribute reprints for Government purposes, notwithstanding any copyright notation herein. Views and conclusions contained in this document are those of the author and should not be interpreted as representing the official policies, either expressed or implied, of Wright Laboratory, the Department of Defense, the United States Government, Siemens Corporation, or Carnegie Mellon University.


1. Introduction
         Connector abstractions are not added in isolation to the UniCon language. Usually, there are one or more component abstractions that must be added at the same time. For now, though, we limit this discussion to the addition of connectors. The following is a step-by-step description of what it takes to add support for a new connector abstraction to the UniCon compiler. We describe the process by phases (i.e., syntax analysis, attribute synthesis, semantic analysis, connection checks, and system building) for simplicity.


2. The UniCon Compiler-Generator
         The UniCon compiler generator is called "Generate_UniCon". To generate a new UniCon compiler, make sure that the directory where the compiler generator is located is in your PATH environment variable, and then simply type "Generate_UniCon". The generator will ask you for a destinatation directory path in which it will place the source code for the newly generated compiler. This path can be either an absolute path name or a relative path name. The generator will check for the existence of the destination directory. If it does not exist, construction of the compiler will be terminated.
         The generator consists of a Bourne shell script that is the master controller, and five Perl scripts called by the master controller that each generate a phase of the compiler. The following is the list of Perl scripts, and the UniCon compiler source files that each generates:
Generator Perl ScriptUniCon Compiler Sources
Generate_Syntax_Phase uni_error.h, uni_error.c, uni_do_decls.h, uni_global.hh, uni_global.h, uni_lexer.l, uni_parser.y, uni_semantic.h, uni_tree_builder.h, uni_tree_builder.c
Generate_Semantic_Phase uni_semantic.h
Generate_Main_and_Utilities uni_main.c, uni_list.h, uni_list.c, uni_unparse.h, uni_unparse.c, uni_user_defined.h, uni_user_defined.c, uni_utility.h, uni_utility.c
Generate_Build_Phase uni_builder.h, uni_builder.c, uni_datause.h, uni_datause.c, uni_fileio.h, uni_fileio.c, uni_pipe.h, uni_pipe.c, uni_proccall.h, uni_proccall.c, uni_rproccall.h, uni_rproccall.c, uni_rts.h, uni_rts.c
Generate_Odinfile Odinfile
         The generator is essentially a smart preprocessor. The source files for the UniCon compiler exist as templates from which all code fragments relating to connector expertise have been extracted. At these extraction points, preprocessor directives of the form "$(some_keyword_identifier)" have been left to mark special places in the code where connector expertise must be added. A preprocessor directive instructs a Perl script to look through the connector expertise database for any code fragments that are related to the keyword identifier. If any are found, then the Perl script inserts the code fragments at the location of the preprocessor directive, and extracts it from the file. The generator is "smart" in that it will not blindly insert code fragments just because they exist in the database for a particular connector. If a connector has not been specified in the generator configuration file, then code fragments for that connector are typically ignored. Similar processing occurs for attribute-value pairs specific to particular connectors. Code fragments for checking and processing these attribute-value pairs are omitted if the attributes are not specified in the database. Code fragments in the database associated with a particular preprocessor directive can, however, be marked for insertion if the connector it supports is absent from the configuration file! (see sections 4.2.4.3, Function Prototypes, and 4.3.2 for more information on this)



3. The Database
         The database for the UniCon compiler generator exists in the Unix file system as a collection of files organized in a series of subdirectories under a directory tree root. It consists of three main parts: the extracted connector expertise, the compiler sources, with and without the embedded preprocessor directives in them, and the generator configuration file. Each of these three parts is described further below. The "Generate_UniCon" Bourne shell script and the other generator Perl scripts exist at the root of the directory tree, and will not be discussed further in this paper.

3.1. Connector Expertise
         The extracted connector expertise is organized by connector expert. There exists a subdirectory in the database for each expert; it contains all the source files, enumeration literals, and code fragments required to support its associated connector abstraction in the UniCon language. Each code fragment is stored in a file with a well-defined name in the expert's subdirectory. Currently there are seven connector abstractions in UniCon, and each has an associated subdirectory in the database. The name of the subdirectory is the name of the connector expert in the database. For now, the expert name in the database must be lower case. We may change this in the future. The table below lists the connector abstractions in UniCon, the type name for it in the UniCon language, and the name of the connector expert in the database:


         The name and purpose of each of the files that an expert can have in its subdirectory of the database is the focus of the rest of this paper. The table below lists these files by name, and describes whether or not they are required or optional. The rest of this paper describes the name, purpose, contents, and content format for each of the files. The token "<expert>" in each of the file names below is replaced with the database expert name, and may or may not exist in a given expert's subdirectory, depending upon whether or not it is required, and (if optional) whether or not it is necessary to support a given connector abstraction.


         The File Status column in Table 3 describes whether or not a file must exist in the database for a connector. If the file is required, then it must be present in the connector expert's subdirectory in the database. If it is not there, the generator will abort the compiler generation process with the appropriate error message. If the file is optional, then it is safe to omit the file from the database. If the file is omitted from the database, the generator will successfully produce a compiler without the associated code fragment or functionality. For example, if the attribute-value pair specification file (see section 4.1 3 below) is omitted for a given connector expert, the compiler will be generated without attribute-value pairs for that connector.

3.2. Compiler Sources
         The second part of the database consists of the source code for the UniCon compiler. The source code that contains preprocessor directives is stored in a subdirectory called "templates." The source code that remains intact without any preprocessor directives is stored in a subdirectory called "remaining_sources." This organization facilitates a better design of tasks for the generator.

3.3. The Generator Configuration File
         The generator configuration file is named "experts" and exists at the root of the directory tree in the database. This file contains the list of connectors that are to be included in the generated UniCon compiler. The format of this file must be strictly adhered to. Each line must contain exactly one connector name. Each name must be the database expert name as it appears in Table 2, above. The generator treats the connector name in a case insensitive manner, and it ignores white space in each line and blank lines.
         Below is a pictoral representation of the structure of the entire database:
        

Figure 1: Database Tree Structure


4. Adding a Connector
         The rest of this paper describes the steps necessary for adding support for a new connector abstraction to the UniCon compiler. These steps are broken down by phases of the compiler, and roughly correspond to each Perl script in the generator.
         Note: When adding enumeration literals to the database, it is recommended that the following style for these literals be adhered to: the enumeration literals should contain neither numerals nor the underscore character. Each literal should be in mixed case, where the first letter (and any other important character) should be capitalized.
         Also, when adding new functions to the compiler, all globally declared functions must have names that begin with "uni_".
         4.1. Creating the New Connector Expert
         The first step in creating support for a new connector is to name the expert and create an associated subdirectory for it in the database (see the third column of Table 2 above). To illustrate, assume we want to add a connector to UniCon that creates BSD socket connections between Process components. Assume further that the UniCon type name for this connector abstraction is Socket. We would name the expert the "socket" expert and create a subdirectory named "socket" under the directory tree root in the database.
         4.1.1. Adding the UniCon Connector Type Name
         Create the following file in the connector expert's database subdirectory (it is required):
         <expert>_connector_enum
         This file must contain only one line. The line must be terminated by a newline and contain only the name of the connector. The name must be the UniCon connector type name as it appears in column 2 of Table 2 above. It will be used as an enumeration literal in the "connector_e" enumeration type in "uni_semantic.h." Therefore, it is recommended that the first character of the name be capitalized. The generator will ignore any whitespace on the line, and it will also ignore any other characters on the line after the first name that appears.
         In our socket connector expert example, the UniCon connector type name is Socket, so in the "socket" subdirectory we create the file "socket_connector_enum". This file contains a single line, terminated by a newline, with the name Socket on the line:
         Socket\n
[EOF]
         4.1.2. Adding the Role Names
         In addition to the role names, the default values for the MinConns and MaxConns attributes for each role and the list of players that each role will accept must be added to the database. Create the following file in the connector expert's database subdirectory (it is required):
         <expert>_role_enums
         This file will contain a role specification for each role in the new connector. Each role specification consists of two or more lines, as described below. Each of these lines must be terminated by a newline.
         The first line of a role specification contains three values. The first value is the UniCon role name, the second is the default value for the MinConns attribute for the role, and the third is the default value for the MaxConns attribute. The UniCon role name will actually be used to construct the "role_e" enumeration type in "uni_semantic.h." Therefore, it is recommended that the first character of the name be capitalized. The three values on this line must be separated by white space, and the entire line must be terminated with a newline. The MinConns and MaxConns values must either be an integer or the word MAXINT (uppercase preferred, but not necessary).
         Subsequent lines of a role specification contain the component/player type combinations for which the role will accept a connection. These accept lines contain three values as well, all separated by white space. The first value is the word "accepts" (the case and any preceding white space for this word are ignored). The second value is the component type, and the third value is the player type. The component and player types must together represent a valid combination of component and player types that the role of the new connector will accept in any connection. Additionally, these two values must be the enumeration literals from the corresponding "component_e" and "player_e" enumeration types in the compiler's "uni_semantic.h" include file. There may be more than one accept line for a given role.
         In our Socket connector example, we define the following UniCon roles: Initiator and Responder. The Initiator role of a socket connection creates a socket in a process initiating communication, initializes it, and starts executing the protocol for initiating communication. The Responder role creates a socket in a process listening for communication requests, initializes it, and starts executing the protocol for listening for communication requests. Assume for the sake of our example that the default MinConns and MaxConns values for the Initiator role are 1, the default MinConns value for the Responder role is 1, and the default MaxConns value for the Responder role is MAXINT. Also assume that the the Initiator role accepts the component/player combination Process/StreamOut, and that the Responder role accepts the combination Process/StreamIn. The
"socket_role_enums" file is illustrated below:
         Initiator 1 1\n
accepts Process StreamOut\n
Responder 1 MAXINT\n
accepts Process StreamIn\n
[EOF]
         4.1.3. Adding Connector-Specific Attribute-Value Pairs
         Connectors often have attribute-value pairs that help qualify the type of connection desired when a connector is instantiated. To add connector-specific attribute-value pairs to the compiler, create the following file in the connector expert's database subdirectory (it is optional):
         <expert>_connector_attribute_enums
         This file will contain a specification for each connector-specific attribute-value pair. Each attribute-value pair specification in the file will exist on a single line, and that line must be terminated by a newline.
         Each attribute-value pair specification in the file has seven values, each separated by white space. All values (except for the second one, which is described below) are required to be present on the line. The first value will be used to construct the "attribute_e" enumeration type in the compiler's "uni_semantic.h" include file. Therefore, it is recommended that the first letter (and any other important letters) be capitalized. This value is the name of the attribute for the attribute-value pair.
         The second value is optional. It is a comment that accompanies the first value into the type definition for the "attribute_e" enumeration type in "uni_semantic.h." This value must be specified using the syntax of a C language comment, and must be separated from the first and third values by white space. The comment will be included in "uni_semantic.h" on the same line on which the enumeration literal for the attribute will be defined.
         The third value should be the UniCon connector type name as it appears in the file
"<expert>_connector_enum"
         The fourth value is the "merge rule" for the attribute. It describes the processing for the values of the attributes that must occur if duplicate attributes are encountered in the same attribute-value pair list. The three legal values that will be accepted here are ERROR, REPLACE, and MERGE. If any other value is present here, the generator will abort. Capitalizing the entire word is recommended, but not necessary. A value of ERROR indicates that subsequent values in duplicate attribute-value pair specifications will be ignored. A value of REPLACE indicates that previous values will be discarded. A value of MERGE indicates that all values in all duplicates will be considered.
         The fifth value is the "required rule" for the attribute. It describes whether or not the attribute-value pair must be specified for a connector definition/instantiation. The two legal values that will be accepted here are REQUIRED and OPTIONAL. If any other value is present here, the generator will abort. Capitalizing the entire word is recommended, but not necessary. A value of REQUIRED means that the attribute-value specification must be specified for a connector definition/instantiation. A value of OPTIONAL means that it may be missing.
         The sixth value must either be "definition," "instantiation," or "both." It is recommended that the value be lowercase, but that is not necessary. A value of "definition" means that if the attribute-value pair is specified, then it can only appear in the connector definition's attribute-value pair list. If the value is "instantiation," then the attribute-value pair can only appear in the connector instantiation's list. If the value is both, then it can appear in either list.
         The seventh value specifies the syntax for the value of the attribute. The compiler's "uni_semantic.h" include file contains macro definitions for all the different forms that an attribute's value might have. Right now, the only forms recognized by the generator are: RLN_LIST_SYNTAX1, IOMODE_SYNTAX, ITEM_SYNTAX, INTEGER, and TRI_QID_SYNTAX. Others forms can be added to the generator on an as-needed basis. For now, however, any other form encountered will cause the generator to abort.
         In our Socket example, assume we wish to add three attribute-value pair specifications. The first is Protocol, the second is Port, and the third is Address. For the Protocol attribute-value pair, we wish the value to be either UDP or TCP, specifying the communication protocol for the socket. Therefore, we choose the syntax to be ITEM_SYNTAX, meaning that the value can be specified with or without double-quotes, with only one value allowed per attribute-value pair specification (i.e., a list of values separated by commas is illegal). We wish to only accept the first attribute-value pair in the list, so we will ignore duplicates. The merge rule we choose, therefore, is ERROR. This attribute is required because we cannot create a socket (in the implementation) without this information. Therefore, the required rule value is REQUIRED. The attribute-value pair can be specified in either the definition or instantiation list.
         The syntax for the Port attribute is INTEGER, meaning that a single integer between parenthesis is the only legal value. This attribute is used to specify the Unix communication port to use for the socket's communication. We choose the same merge rule and instantiation/definition values that we chose for the Protocol attribute, for the same reasons. We choose a required rule of OPTIONAL, however, because we can let UniCon figure out the next available socket port number in some cases. In those cases where UniCon cannot figure this out, a semantic error check will be performed to see that the Port attribute is specified.
         The syntax for the Address attribute is ITEM_SYNTAX. This is true because the address is going to be in the standard Unix hostname format, a dot-separated list of integers or identifiers (i.e., "springer.arch.cs.cmu.edu", or "128.2.198.16"). Since the "." character is a UniCon token, UniCon will not be able to parse the name correctly if it is not in double-quotes. We choose the same merge rule, required rule, and instantiation/definition values that we chose for the other two attributes, for the same reasons.
         With the above three attributes in mind, our new "socket_connector_attribute_enums" file will look like the following:
         Protocol Socket ERROR REQUIRED both ITEM_SYNTAX\n
Port /* this one gets a comment */ Socket ERROR OPTIONAL both INTEGER\n
Address Socket ERROR REQUIRED both ITEM_SYNTAX\n
[EOF]
         4.1.4. Adding Role-Specific Attribute-Value Pairs
         To add role-specific attribute-value pairs for a connector, create the following file in the connector expert's database subdirectory (it is optional):
         <expert>_role_attribute_enums
         The format of this file is identical to the format of the "<expert>_connector_attribute_enums" file (see section 4.1.3 above) with two exceptions. First, the third value is the UniCon role type name as it appears in the "<expert>_role_enums" file. Next, the sixth value is ignored, but it must be present. Simply always make it "both."
         4.2. The Syntax Phase
         Error handling, lexical analysis, syntax analysis, and tree building are all grouped into this phase of the compiler generation process. Additionally, some of the global "include" files are generated in this phase.
         4.2.1. Error Handling
         Error handling in the UniCon compiler has been designed so that error messages are easier to maintain. Error messages exist in the global array "uni_error_messages" in the file "uni_error.c," and each has a corresponding C macro definition in the "uni_error.h" file that defines the index into the array corresponding to the position of the associated error message. The error messages in the UniCon compiler are organized into three severity groups: error, warning, and informational. The first character in a message of severity error must be a `?'. For warning messages, the character must be a `%'. For informational messages it must be a `#'.
         To add new error messages to the compiler there are potentially three new files to create, one for each severity level. Each has the same format which is described below. To add error messages of severity level error, create the following file in the connector expert's database subdirectory (it is optional):
         <expert>_error_messages
         To add error messages of severity level warning, create the following file in the connector expert's database subdirectory (it is optional):
         <expert>_warning_messages
         To add error messages of severity level informational, create the following file in the connector expert's database subdirectory (it is optional):
         <expert>_informational_messages
         The format of the contents of each of these files is identical. Each error message specification is required to be placed on a line by itself in the file. Each line in the file must be terminated by a newline. Each error message specification has two parts, separated by white space: the C macro name and the error message itself. It is recommended that the macro name be in uppercase, but it is not necessary. The macro name, however, must be exactly eight characters in length. The error message must follow the macro name on the same line, separated by white space. It can be any length and contain any characters. Do not include the `?', `%', and `#' characters in the error message itself. The generator will add them. Just include the text of the messages. Additionally, do not include any newline characters in the messages, and do not surround the message by double quotes. Double quotes are added by the generator, and lines breaks are automatically added by the UniCon compiler at run-time. Finally, the error message should be written in the C language syntax for character strings.
         In our Socket example, assume we wish to add the following error message of severity error:
"? Error checking value for `Protocol' attribute: \"%s\" unrecognized - value must be \"UDP\" or \"TCP\"".
         If we choose the macro name to be INVPRTCL, then our "socket_error_messages" file will look like:
         INVPRTCL Error checking value for `Protocol' attribute: \"%s\" unrecognized - value must be \"UDP\" or \"TCP\"\n
[EOF]
         In the above example, assume the the `\n' is part of the file contents, and not the error message.
         4.2.2. New Value Syntax for Attribute-Value Pairs
         If the syntax of the value of any new attribute-value pairs is new to the compiler, then the "yacc" script (in "uni_parser.y") must be modified to accept this new syntax. Additionally, the "av_pair_t" datatype in
"uni_semantic.h" must be modified so that the information in the value portion of the new attribute-value pair can be added to the compiler's internal attributed syntax tree. Also, a new function will have to be created in "uni_tree_builder.c" to actually add this information to the attributed syntax tree. Currently, thirteen of the most common forms of syntax are included in the compiler. The addition of this new syntax is beyond the scope of this paper. If you need to add new syntax, contact the developers of the UniCon compiler.
         4.2.3. Lexical Analysis and Syntax Analysis
         Nothing currently gets added to the lexical analysis routines (in the "lex" script) or in the syntax analysis routines (in the "yacc" script), except as described in section 4.2.2 above.
         4.2.4. Tree Building
         The tree building expertise for each new connector consists of code fragments that synthesize attributes in the compiler's internal attributed syntax tree representation of the UniCon input. Here the term "attributes" refers to "lexical attributes" and "synthesized attributes" from classical descriptions of compiler implementations.
         Sometimes it is necessary (e.g., for efficiency) to construct new data values from attributes of nodes lower down in the syntax tree and insert these values as attributes in nodes higher up in the tree. There are two locations in the compiler where this type of attribute synthesis is performed to support implementation of connectors. It is performed on UniCon attribute-value pairs in the attribute-value pair lists of connector protocols and roles.
         Attribute synthesis is organized in the compiler in such a way that individual UniCon attribute-value pairs have their own code fragments. This is because the processing of each attribute-value pair is unique and is easily added or subtracted from the compiler based on whether or not the attribute has been specified in the "<expert>_connector_attribute_enums" and "<expert>_role_attribute_enums" files.
         4.2.4.1. Protocol Attribute-Value Pair Lists
         To add attribute synthesis code fragments that synthesize attributes for protocol attribute-value pairs, create the following file in the connector expert's database subdirectory (it is optional):
"<expert>_protocol_av_pair_attribute_synthesis"
         For each UniCon attribute-value pair for which syntax tree attribute-synthesis is required, the
"<expert>_protocol_av_pair_attribute_synthesis" file must contain the following:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums"), preceded by a dollar sign character, `$', on a line by itself that is terminated by a newline (e.g., $Protocol)
         2. a function called "synthesize_<attribute name>_attributes" that follows the line with the `$<attribute name>' on it, and that must be declared in the following manner:

static void synthesize_<attribute name>_attributes

#ifdef __STDC__
(impl_t *impl, av_pair_t *av_pair)
#else
(impl, av_pair)
impl_t *impl;
av_pair_t *av_pair;
#endif

{
<specific code goes here>
}
         3. any statically declared support functions and global data required by the
"synthesize_<attribute name>_attributes" function; these must be included after the line with the attribute name on it, and before the
"synthesize_<attribute name>_attributes" function
         Indentation in the file should start at 0 spaces. Each line in the file "<expert>_protocol_av_pair_attribute_synthesis" must be terminated by a newline.
         In our Socket example, assume that the attribute-value pair Protocol requires some attribute synthesis. The contents of our file look like the following:
         $Protocol\n
/*
* This is a comment that will precede our function definition. It will be
* added to the generated UniCon compiler along with the code fragment.
*/
static void synthesize_Protocol_attributes

#ifdef __STDC__
(impl_t *impl, av_pair_t *av_pair)
         #else
(impl, av_pair)
impl_t *impl;
av_pair_t *av_pair;
#endif
{
<specific code goes here>
}
         If more than one UniCon attribute-value pair requires attribute synthesis, all of the code for every attribute gets added to the same "<expert>_protocol_av_pair_attribute_synthesis" file, sequentially. For example, assume that all three of our Socket expert's attribute-value pairs require attribute synthesis. Our
"socket_protocol_av_pair_attribute_synthesis" file would look like this:
         $Protocol\n
.
. ("synthesize_Protocol_attributes" definition goes here)
.
$Port\n
.
. ("synthesize_Port_attributes" definition goes here)
.
$Address\n
.
. ("synthesize_Address_attributes" definition goes here)
.
[EOF]
         The $__always__ Marker
         In step number one described above, if "__always__" (i.e., the word "always" with two underscore characters appended to both the beginning and end) is substituted for the attribute name of the attribute-value pair specification, the function and global data definitions that follow in the file are added to the compiler unconditionally.
         4.2.4.2. Role Attribute-Value Pair Lists
         To add attribute synthesis code fragments that synthesize attributes for role attribute-value pairs, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_protocol_attribute_synthesis"
         For each UniCon attribute-value pair for which syntax tree attribute-synthesis is required, the
"<expert>_protocol_attribute_synthesis" file must contain the following:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums"), preceded by a dollar sign character, `$', on a line by itself that is terminated by a newline
         2. a code fragment to be placed in a C language "case" statement in the compiler that extracts data from the UniCon attribute-value pair and inserts it into the compiler's "role_t" syntax tree node for the role
         Indentation in the file should start at 0 spaces. Each line in the file "<expert>_protocol_attribute_synthesis" must be terminated by a newline.
         NOTE: The $__always__ marker does not apply in this case (see section 4.2.4.1 above).
         It is very rare that a new role attribute-value pair will need to be added to the UniCon compiler. In our Socket example, we do not have any role attribute-value pairs that we must add. However, if we hypothetically had one called Handshake, for example, the contents of our "socket_protocol_attribute_synthesis" file look like:
         $Handshake\n
<specific role attribute code fragment goes here>
[EOF]
         Multiple attributes and their code fragments are added to the same "<expert>_protocol_attribute_synthesis" file, similar to the way that multiple protocol attribute-value pairs are added to the same
"<expert>_protocol_av_pair_attribute_synthesis" file as described in section 4.2.4.1 above.
         4.2.4.3. Syntax Phase Include Files
         One of the include files that gets generated during the syntax phase of the compiler generation process is the "uni_semantic.h" file. Some connector experts may require connector-specific C type definitions and function prototypes to be declared in this file. This section describes how to do both.
         C Type Definitions to support Syntax and Semantic Checking
         To add connector expert-specific C type declarations to "uni_semantic.h," create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_enum_types"
         In this file, simply include the actual C language definitions for the C types that should be included in
"uni_semantic.h." The code fragments should be written as compilable, error-free C language type definitions.
         Indentation in the file should start at 0 spaces. Each line in the "<expert>_enum_types" file must be terminated by a newline.
         Assume for the sake of our Socket example that we need a new enumeration type in "uni_semantic.h," called "protocol_e," to support semantic checking. The contents of our "socket_enum_types" file would look like this:
         /*
* We add an enumeration type for socket protocols to aid in semantic checking.
*/

typedef enum {
UDP, /* connection-less protocol */
TCP /* connection-oriented protocol */
} protocol_e;
[EOF]
         Function Prototypes
         To support semantic checking of individual connector types, each expert may require the addition of one or more functions to the "uni_semantic.c" file of the compiler. These functions, if not statically declared, may also require corresponding prototypes to be added to the "uni_semantic.h" include file. The functions may be added to the compiler unconditionally for an expert, or they may be added in the presence or absence of a particular attribute-value pair or connector expert. All of these situations are described below.
         To add functions prototypes to "uni_semantic.h" (we describe the addition of the functions themselves in section 4.3.2 below), create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_semantic_support_defs"
         This file is used by two of the Perl scripts of the generator. The Generate_Syntax_Phase Perl script uses it to add function prototypes to "uni_semantic.h." The Generate_Semantic_Phase Perl script uses it to add the corresponding function definitions to "uni_semantic.c." Because this file is used for both purposes, it's format is slightly more complicated than that of the other database files.
         For prototypes that get included in the presence of a particular attribute-value pair or connector expert, the
"<expert>_semantic_support_defs" file must contain the following:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums") or the name of the connector expert (as it appears in the file "<expert>_connector_enum"), preceded by a dollar sign character, `$', followed by white space and then the word "prototypes" (preferably in lowercase letters, but this is not required), followed by a newline (e.g., $Protocol prototypes\n)
         2. a static or global function prototype declaration that follows the line with the `$<attribute name>' on it, using C language syntax declared to be both ANSI C and K&R C compatible, as follows:

int function_prototype_name (

#ifdef __STDC__
arguments
#endif
);
         For prototypes that get included in the absence of a particular attribute-value pair or a connector expert, insert the word "no" between the dollar sign and attribute or connector name in instruction step number one (e.g., $noProtocol). There should be no white space between the word "no" and the dollar-sign and attribute or connector name. It is recommended that the word "no" contain only lowercase letters, although this is not necessary. For prototypes that get included unconditionally, substitute the "__always__" marker in place of the attribute or connector name in instruction step number one.
         Indentation in the file should start at 0 spaces. Each line in the file "<expert>_semantic_support_defs" must be terminated by a newline.
         In our Socket example, assume that the semantic checks for the attribute-value pair Protocol require a function to check wether or not the value of the attribute is legal. The contents of our "socket_semantic_support_defs" file look like the following (note that our global function definition's name starts with "uni_" because it it globally declared):
         $Protocol prototypes\n
int uni_valid_protocol (
#ifdef __STDC__
string_t *string,
char **message
#endif
);
[EOF]
         Multiple function prototypes can be grouped under a single attribute-value pair or connector marker in the file.
         4.3. The Semantic Phase
         The semantic phase of the UniCon compiler generator builds only the "uni_semantic.c" file, which contains all of the semantic checking routines in the compiler.
         4.3.1. Builtin Connectors
         Some connector experts support automatic connection of players that have not been explicitly connected by a CONNECT or ESTABLISH statement in a UniCon definition of a composite component. This type of support requires that there be a builtin definition of the connector since the user will not explicitly instantiate (and therefore, provide as input) a UniCon definition of the connector.
         The generator will automatically create a builtin connector in the UniCon compiler when the connector expert has the following file in it's database subdirectory (it is optional):
         "<expert>_builtin_connector"
         The file should remain empty (its contents are ignored). It's mere present will indicate to the generator that a builtin version of the connector should be constructed and included in the compiler. The name of the builtin connector will be "Builtin_<connector name>," where <connector name> is the enumeration literal for the connector expert as it appears in the file "<expert>_connector_enum." The builtin connector is a static data definition of type "conn_t *". It is declared to be "static" because it is not needed outside of the "uni_semantic.c" file where all of the automatic connections are made.
         In our Socket example, we include the file "socket_builtin_connector" in the socket expert's database subdirectory. The generator creates the builtin connector "Builtin_Socket."
         4.3.2. Semantic Checking Support Functions
         To support semantic checking of individual connector types, each expert may require the addition of one or more functions to the "uni_semantic.c" file of the compiler. The functions may be added to the compiler unconditionally for an expert, or they may be added in the presence or absence of a particular attribute-value pair or connector expert. Both of these situations are described below.
         To add support functions to "uni_semantic.c", create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_semantic_support_defs"
         This file may have already been created to add the corresponding function prototypes (see section 4.2.4.3, Function Prototypes). If so, then simply add to the existing file. The format for the function definitions in this file is identical to the format of the prototypes.
         For functions that get included in the presence of a particular attribute-value pair pr cpmmectpr expert, the
"<expert>_semantic_support_defs" file must contain the following:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums") or the name of the connector expert (as it appears in the file "<expert>_connector_enum"), preceded by a dollar sign character, `$', followed by white space and then the word "definitions" (preferably in lowercase letters, but this is not required), followed by a newline (e.g., $Protocol definitions\n)
         2. a static or global function definition that follows the line with the `$<attribute name>' on it, using C language syntax declared to be both ANSI C and K&R C compatible, as follows:

int function_name (

#ifdef __STDC__
(arguments)
#else
(argument names)
argument declarations
#endif
{
<specific code goes here>
}
         For functions that get included in the absence of a particular attribute-value pair or connector expert, insert the word "no" between the dollar sign and attribute or connector name in instruction step number one (e.g., $noProtocol). There should be no white space between the word "no" and the dollar-sign and attribute or connector name. It is recommended that the word "no" contain only lowercase letters, although this is not necessary. For prototypes that get included unconditionally, substitute the "__always__" marker in place of the attribute or connector name in instruction step number one.
         Indentation in the file should start at 0 spaces. Each line in the file "<expert>_semantic_support_defs" must be terminated by a newline.
         In our Socket example, assume that the semantic checks for the attribute-value pair Protocol require a function to check wether or not the value of the attribute is legal. The contents of our "socket_semantic_support_defs" file look like the following (note that our global function definition's name starts with "uni_" because it it globally declared):
         $Protocol definitions\n
int uni_valid_protocol

#ifdef __STDC__
(string_t *string, char **message)
#else
(string, message)
string_t *string;
char **message;
#endif

{
<function specific C code goes here>
}
[EOF]
         See the corresponding prototype declaration for this function in section 4.2.4.3, Function Prototypes.
         Multiple functions can be grouped under a single attribute-value pair or connector marker in the file.
         4.3.3. Initialization Function Calls
         The semantic analysis code in the compiler has an initialization routine, "uni_init_evaluator," that gets called before any semantic checking occurs. If the semantic checking code fragments added to the UniCon compiler for a particular expert need some initialization, then a parameterless initialization function can be added as a semantic checking support function (see section 4.3.2) and called from "uni_init_evaluator." To add the function call to "uni_init_evaluator," create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_init_routine_calls"
         The contents of this file are calls to existing initialization routines, in the C language syntax for function calls. Indentation should start at 4 spaces. Each line in this file must be terminated by a newline.
         In our Socket example, assume we have added a parameterless initialization function named "init_protocols" to "uni_semantic.c," returning "void," via the database file "socket_semantic_support_defs." We now want to add the call to this function to the "uni_init_evaluator" function via the file "socket_init_routine_calls." Our file looks like this:
         init_protocols ();\n
[EOF]
         4.3.4. Duplicate Attribute-Value Pair Checks
         Much of the syntax and semantics checking in the compiler is devoted to attribute-value pairs. There is a function in "uni_semantic.c" named "uni_ duplicate_av_pair" that checks for duplicate attribute-value pair specifications in the same attribute-value pair list. A duplicate usually means the existance of more than one of the same attribute (e.g., more than one Protocol attribute) in the same list. However, for some attribute-value pair specifications, more than one of the same attribute are allowed in the same list, and a duplicate depends on the contents of the value portion of the attribute-value pair specification (e.g., it's not a duplicate unless the value portion is identical to the value portion of a previous attribute of the same name in the list).
         For attribute-value pair specifications where a duplicate depends on the value portion of the attribute-value pair specification, a code fragment must be inserted into the "uni_duplicate_av_pair" function to check the values of two attribute-value pair specifications with the same attribute name. At the point where the code fragment is inserted into the function, two variables exist named "tav_pair" and "av_pair." Both of these point to an attribute-value pair specification whose value must be checked with the other to determine whether or not the attribute-value pair specifications are duplicates.
         To specify the code fragments to check for duplicate attribute-value pair specifications for which a duplicate depends on the value portion, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_duplicate_av_pair_checks"
         The contents of the file should be formatted as follows:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums"), preceded by a dollar sign character, `$', on a line by itself that is terminated by a newline (e.g., $Address\n)
         2. a code fragment immediately following the line with the attribute name on it, written in C language syntax, that checks the values of two attribute-value pair specifications to see if they are duplicates, and then uses the C "continue" statement (if they are duplicates) to continue with the next iteration of the loop; the code fragment should use the two variables "tav_pair" and "av_pair," which point to the two specifications to be checked
         Indentation for the code fragment that follows the attribute name in the file should start at 16 spaces. Each line of the file must be terminated by a newline. The file may contain more than one attribute/code fragment combination.
         NOTE: The "__always__" marker does not apply in this case (see section 4.2.4.1 above).
         Although not required, it is highly recommended that the code fragment be implemented as a function, and the call to that function placed in the "<expert>_duplicate_av_pair_checks" file instead of the code fragment itself. The function should return an integer signifying "true" if the two attribute-value pairs are duplicates and "false" otherwise. If they are duplicates, then the "continue" C statement should be executed. The function definition should be added to the "uni_utility.c" file as described in section 4.5.2 below.
         In our Socket example, assume that the Address attribute-value is not a duplicate unless its value duplicates the value of another Address attribute-value pair found in the same list. We create a function called
"uni_is_duplicate_Address" to check the values of two Address attribute-value pairs. The function definition is added to "uni_utility.c" as described in section 4.5.2. Our code fragment in the file
"socket_duplicate_av_pair_checks" looks like this:
         $Address\n
/*
* Is the Address attribute a duplicate?
*/

if (!uni_is_duplicate_Address (tav_pair, av_pair))
continue;

[EOF]
         4.3.5. Connector Attribute-Value Pair Semantic Checks (Pass 1)
         In the semantic analysis phase, the UniCon compiler walks the attributed syntax tree three times performing semantic checks on the input in various stages. During the first pass, the compiler makes semantic checks that are local to each component or connector definition. During the second pass, semantic checks are made that involve going outside the boundary of a given component/connector (e.g., the check that a player exists inside a component specified in a CONNECT statement in another composite component). Lastly, during the third pass the compiler checks the legality of the specified connections inside the implementation of a composite component.
         During the first pass, semantic checks on connector attribute-value pairs include checking the value of the attribute-value pair for legality, but only within the bounds of the given connector definition. For example, in our Socket example the Protocol attribute can take on the values UDP and TCP only. Therefore, a first-pass semantic check for this attribute-value pair would be to check that the specified input string matches one of these two values. In another example, assume we have an attribute, Match, that takes as input a list of player names that are qualified by their component names (i.e., the list is of identifiers of the form id1.id2, where id1 represents a component name and id2 represents a player name). A first pass semantic check for this attribute-value pair would be to cycle through the list and check that each id1 identifier actually names a previously instantiated component (i.e., a component in a UniCon USES statement) in the same module. Checking that id2 names a player in component id1 is not an example of a first-pass semantic check because it involves finding component id1 (going outside the boundary of the component we are currently checking) and looking inside at the players.
         To add code fragments that perform first-pass semantic checks on connector attribute-value pairs, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_connector_attribute_checks"
         The contents of the file should be formatted as follows:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums"), preceded by a dollar sign character, `$', on a line by itself that is terminated by a newline (e.g., $Address\n)
         2. a code fragment immediately following the line with the attribute name on it, written in C language syntax, that performs first-pass semantic checks on the value portion of the attribute-value pair specification
         Indentation for the code fragment that follows the attribute name in the file should start at 16 spaces. Each line of the file must be terminated by a newline. The file may contain more than one attribute/code fragment combination.
         NOTE: The "__always__" marker does not apply in this case (see section 4.2.4.1 above).
         It may be convenient to implement the code fragment (or a portion of it) as a function, and then place a call to that function in the "<expert>_connector_attribute_checks" file instead of the code fragment itself. The function definition would then be added to the "uni_utility.c" file as described in section 4.5.2 below.
         In our Socket example, we need to check that the value of the Protocol attribute-value pair is either "UDP" or "TCP." The contents of the "socket_connector_attribute_checks" look like this:
         $Protocol\n
/*
* Check to make sure that the value specified is a valid protocol by
* calling the "uni_valid_protocol" routine. It it is not valid, report the
* error.
*/

tstring = (string_t *) uni_get_val_from_head (av_pair->value.mixed.list);
if (!uni_valid_protocol (tstring, &message)) {
sprintf (uni_err, message, tstring->string);
uni_report_error (tstring->lineno, uni_err, infile);
}
[EOF]
         Notice that our code fragment calls the routine "uni_valid_protocol" to actually do the checking. This makes the code inherently more maintainable because the knowledge of which protocols are legal are encapsulated in a single routine that can be easily changed over time without having to modify multiple places in the compiler.
         4.3.6. Connector Protocol Semantic Checks
         Sometimes it may be necessary to make connector-specific first-pass semantic checks above and beyond the standard set of checks performed on connector protocols. The standard set includes making first-pass checks on all connector attribute-value pairs, checking each role definition and associated attribute-value pairs, and checking to see that all connector definitions require a minimum of two player/role connections.
         An example of a check that would be included here is as follows. There may be a case where a connector has a set of legal role types, but only a subset of the role types can be legally included in any definition of the connector at any one time. The legal subset would be defined by a connector attribute-value pair. Specifically, assume that you have a connector type FileIO, and the legal role types are Reader and Writer. Further, assume that the FileIO connector has an IOMode attribute-value pair that allows the definer to specify that the connector is ReadOnly, ReadWrite, or WriteOnly. If the definer sets the value of the IOMode attribute-value pair to ReadOnly, then the Write role type is not legal in the connector definition. Any semantic checks on the legality of the role type would qualify as a connector protocol semantic check.
         To add code fragments that perform first-pass semantic checks on connector protocols, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_check_protocol"
         The contents of the file should be formatted as follows:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums"), preceded by a dollar sign character, `$', on a line by itself that is terminated by a newline (e.g., $Address\n)
         2. a code fragment immediately following the line with the attribute name on it, written in C language syntax, that performs first-pass semantic checks on connector protocols
         Indentation for the code fragment that follows the attribute name in the file should start at 4 spaces. Each line of the file must be terminated by a newline. The file may contain more than one attribute/code fragment combination.
         To include protocol-checking code fragments unconditionally, use the "__always__" marker instead of an attribute-value pair name (see section 4.2.4.1 above). To include code fragments in the absence of an attribute-value pair, simply insert the word "no" between the dollar sign and the attribute name, with no white space between the dollar sign and the word "no," and between "no" and the attribute name (e.g., $noAddress\n). The word "no" should preferably be in lowercase letters, but this is not required.
         In our Socket example, assume that if the Protocol is "UDP" that we could potentially have many players play the Initiator role, and that if it is "TCP" we are required to have exactly one. We can enforce this by adding a protocol semantic check in the compiler to check the MaxConns attribute value of the Initiator role based on the value of the Protocol attribute-value pair. The contents of our "socket_check_protocol" file might look like:
         $Protocol\n
/*
* Check to make sure that if the connector type is Socket and the Protocol is TCP,
* that the maximum number of connections to the Initiator role is exactly 1. If it is not,
* then report the error.
*/

if (prot->type == Socket) {
av_pair = uni_find_av_pair (prot->av_pairs, Protocol);
if (av_pair != (av_pair_t *) 0) {
tstring = (string_t *) uni_get_val_from_head (av_pair->value.mixed.list);
role = uni_find_role_by_type_prot (prot, Initiator);
if (role != (role_t *) 0) {
if (strcmp (uni_lower_case (tstring->string), "tcp") == 0) {
if (role->maxconns != 1) {
<generate the appropriate error message>
}
}
}
}
}
[EOF]
         Of course, any new definitions of error messages must be added to the error messages files in the connector expert's database subdirectory (see section 4.2.1 for details). Additionally, any new routines such as
"uni_find_role_by_type_prot" should be added to the file "uni_utility.c" as described in section 4.5.2 below.
         4.3.7. Attribute-Value Pair Semantic Checks (Pass 2)
         There is one function in the UniCon compiler that makes second-pass semantic checks (see section 4.3.5 for a description of second-pass semantic checks) on all attribute-value pairs in the input list of attribute-value pairs. It is called "check_av_pairs_pass2." There are three code fragments that can be potentially added to this function for each individual connector expert. The first is a set of new variable declarations that are to be defined at the beginning of the function to support the second and third code fragments. The second code fragment is added to a huge C "switch" statement to perform second-pass semantic checking on individual attribute-value pairs. The third code fragment performs semantic checking on the list of attribute-value pairs (e.g., the detection of the absence of an attribute-value pair in a list).
         4.3.7.1. Variable Definitions
         There are certain variable definitions that get automatically included in "check_av_pairs_pass2". These are:
         uni_node temp, temp2, temp3;
uni_list uses;
use_t *tuse, *tuse2;
av_pair_t *tav_pair;
player_t *tplayer1, *tplayer2;
relation_t *relation;
connect_t *connect;
int player_type, first_item, matches = 0, rate_monotonic = 1;
int priority = 0, variant = 0, processor = 0, recordfmt = 0, found_it;
int found_some_objects = 0, error = 0, lineno, members, i;
int found_one, found_two, server, triggers = 0, segments = 0;
char *message, *name, *path, *file, *null_infile = "";
string_t *tstring = (string_t *) 0;
location_t *location;
FILE *fd;
         To add variable definitions to support second-pass semantic checks on attribute-value pair lists, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_attribute_checks_defs_pass2"
         NOTE: Before creating this file, you must check the above list of declarations as well as the
"<expert>_ attribute_checks_defs_pass2" file for all other connector experts to see if the variable names you wish to declare are already in use, or the variables have already been declared. The generator does not check this for you. Any conflicts will cause the generator to produce a UniCon compiler that will contain compilation errors.
         The contents of the "<expert>_attribute_checks_defs_pass2" file should be formatted as follows:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums"), preceded by a dollar sign character, `$', on a line by itself that is terminated by a newline (e.g., $Address\n)
         2. a code fragment immediately following the line with the attribute name on it, written in C language syntax, that declares the variables needed to support the other code fragments in the "check_av_pairs_pass2" routine (see sections 4.3.8.2 and 4.3.8.3 below)
         Indentation for the code fragment that follows the attribute name in the file should start at 4 spaces. Each line of the file must be terminated by a newline. The file may contain more than one attribute/code fragment combination.
         To include code fragments in the absence of an attribute-value pair, simply insert the word "no" between the dollar sign and the attribute name, with no white space between the dollar sign and the word "no," and between "no" and the attribute name (e.g., $noAddress\n). The word "no" should preferably be in lowercase letters, but this is not required.
         NOTE: The "__always__" marker does not apply in this case (see section 4.2.4.1 above).
         In our Socket example we need to define the local integer variable "port" in the function
"check_av_pairs_pass2" to support the second pass semantic checks on the Port attribute value pair, and we need to initialize it to 0 (see the description of the Socket example in section 4.3.8.2 below). The contents of our "socket_attribute_checks_defs_pass2" look like this:
         $Port\n
int port = 0;\n
[EOF]
         4.3.7.2. Attribute-Value Pair Semantic Checks
         To add second-pass semantic checks on attribute-value pairs, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_attribute_checks_pass2"
         The contents of the file should be formatted as follows:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums"), preceded by a dollar sign character, `$', on a line by itself that is terminated by a newline (e.g., $Address\n)
         2. a code fragment immediately following the line with the attribute name on it, written in C language syntax, that performs second-pass semantic checks on the value portion of an attribute-value pair specification
         Indentation for the code fragment that follows the attribute name in the file should start at 16 spaces. Each line of the file must be terminated by a newline. The file may contain more than one attribute/code fragment combination.
         To include code fragments in the absence of an attribute-value pair, simply insert the word "no" between the dollar sign and the attribute name, with no white space between the dollar sign and the word "no," and between "no" and the attribute name (e.g., $noAddress\n). The word "no" should preferably be in lowercase letters, but this is not required.
         NOTE: The "__always__" marker does not apply in this case (see section 4.2.4.1 above).
         In our Socket example, assume that if the Port attribute is not present the default value picked by UniCon for the socket port will be 8001, and that the user must be given a warning message describing that this default value has been chosen.
         One way to implement this is to set a variable when the Port attribute is encountered in a particular list, and then check this variable after the list has been processed to see if the attribute was found. To implement this in our example, we choose to set the integer variable "port" to the value "1" if we encounter it. Otherwise it is left alone (it's initial value will be set to "0" - see the Socket example description in section 4.3.8.1 above). This must be done in the second code fragment that gets added to the large C "switch" statement in
"check_av_pairs_pass2" (see section 4.3.8 above). Therefore we add this code fragment to the file
"socket_attribute_checks_pass2." The contents of this file look like this:
         $Port\n
/*
* Make a note that we've found a Port attribute-value pair.
*/

port++;

[EOF]
         Note that the C "case" and "break" statements that surround the code fragment are added by the generator and do not need to be included in the code fragment.
         4.3.7.3. Attribute-Value Pair List Semantic Checks
         When making second-pass semantic checks on attribute-value pairs, there may be a need to make a check on a list of attribute-value pairs, rather than on a single one. For example, it may be desirable to check for the absence of a particular attribute-value pair specification in a list, and then report a warning message to the user that a particular default value is being assumed.
         To add a code fragment to "check_av_pairs_pass2" to perform semantic checks on an entire attribute-value pair list, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_attribute_checks_final_pass2"
         The contents of the file should be formatted as follows:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums"), preceded by a dollar sign character, `$', on a line by itself that is terminated by a newline (e.g., $Address\n)
         2. a code fragment immediately following the line with the attribute name on it, written in C language syntax, that performs second-pass semantic checks on an attribute-value pair list as a whole
         Indentation for the code fragment that follows the attribute name in the file should start at 8 spaces. Each line of the file must be terminated by a newline. The file may contain more than one attribute/code fragment combination.
         To include code fragments in the absence of an attribute-value pair, simply insert the word "no" between the dollar sign and the attribute name, with no white space between the dollar sign and the word "no," and between "no" and the attribute name (e.g., $noAddress\n). The word "no" should preferably be in lowercase letters, but this is not required.
         NOTE: The "__always__" marker does not apply in this case (see section 4.2.4.1 above).
         In our Socket example, we wish to check for the presence of the Port attribute. If it is not found, we wish to give the user a warning that the default value (port number 8001) is being chosen. In section 4.3.8.2 above, we incremented the "port" variable to be the value "1" if we encounter the Port attribute in the list. So, here is where we check this value and report a warning if necessary. Our code fragment in the file
"socket_attribute_checks_final_pass2" looks like this:
         $Port\n
/* If the attribute-value pair list is from a Socket connector
* instantiation, and no Port attribute was found, print out
* a warning that Unix socket port number 8001 is being assumed
* as the default port value.
*/

if ((use->u.p.conn->type == Socket) && (!port)) {
sprintf (uni_err, uni_error_messages[NOPORTFD],
use->u.p.conn->name->string);
uni_report_error (use->lineno, uni_err, infile);
}

[EOF]
         We also add the appropriate error message to the "<expert>_warning messages" file as described in section 4.2.1 above.
         4.3.8. Semantic Checks of Connections
         The UniCon semantic analyzer contains a function named "check_connects" that performs semantic checking on connections made in a composite component. This function localizes all of the connector expertise in the compiler that performs connection checks, and there are potentially nine code fragments for a connector expert that are used by the generator to create the "check_connects" function. All of these code fragments are described in this section. The algorithm for "check_connects" is as follows:
         variable definitions code fragment

initialization code fragment
for each connection:
initial semantic checks code fragment
for each portion of a connection:
initial semantic checks code fragment
signature checks code fragment
final semantic checks code fragment
final semantic checks code fragment
final semantic checks code fragment
         4.3.8.1. The "Find_Definer_Connect" Function
         NOTE: If the connector you are adding does not involve players with signatures that must be checked, this section does not apply.
         The "find_definer_connect" function in the semantic analyzer is used by the "check_connects" function to find the portion of a complete connection involving the "definer" role (in UniCon, a CONNECT statement usually involves at most 1/2 of a complete connection, or maybe less). "check_connects" uses the signature of the player connected to the "definer" role to perform signature checking on the other players in the connection. The reason that the word "definer" is double-quoted here is to emphasize that the role does not have to actually be a Definer role. It can be any role that the connector designer wishes to use as the basis of comparison during signature checking. For example, the UniCon compiler performs signature checking on players hooked to a Pipe connector. The "definer" role in this case is the first StreamIn or StreamOut player that UniCon encounters. The "find_definer_connect" routine returns a pointer to the "connect_t" struct that contains the "definer" portion of the connection.
         To add a code fragment to "find_definer_connect" to return the appropriate "definer" portion of a connection for a given connector expert, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_check_connects_find_definer"
         The code fragment should be enclosed in an "if" statement that checks the connector type. It should cycle through the "econn_list" member of the "establish" struct looking for the portion of the connection that contains the role of the appropriate type, and return the pointer to that portion of the connection. If that portion of the connection is not found in the "establish," there is a serious error in the compiler's internal state. In this case, the code fragment should abort the compilation and report a system error to the user.
         Indention for the code fragment should start with 4 spaces and each line in the file must be terminated with a newline.
         In our Socket example, assume that we will perform signature checking on the StreamIn and StreamOut players hooked to the connector (the semantics of a Socket connection are similar to the semantics of a Pipe connection in many ways, including signature checking). We must add a code fragment to "find_definer_connect," and the code fragment will return the first StreamIn or StreamOut portion of a connection that it encounters. The contents of our "socket_check_connects_find_definer" file look like this:
         if (establish->use->u.p.conn->type == Socket) {
loop_through_uni_list (establish->econn_list, temp, connect, connect_t *) {
if ((connect->rhs->u.role->type == StreamIn) ||
(connect->rhs->u.role->type == StreamOut))
return connect;
}
sprintf (uni_err, uni_error_messages[SYSTEMER], "find_definer_connect",
"no Definer role found in ");
fprintf (stderr, "%s", uni_err);
fprintf (stderr, "%s connector\n", establish->use->u.p.conn->prot->type_str->string);
exit (4);
}
[EOF]
         4.3.8.2. Variable Definitions
         There are certain variable definitions that get automatically included in "check_connects". These are:
         av_pair_t *tav_pair;
char *message, *infile = "", *member_pattern = "member `%s' of ";
char *member1, *member2, *match, *match_string1 = "";
char *match_string2 = "the `match' attribute for ";
char *match_string3 = "the default `match' attribute for ";
uni_node temp, temp2, temp3, temp4;
establish_t *establish, *testablish;
connect_t *connect, *definer_connect;
player_t *tplayer1, *tplayer2;
qname_t *save_player;
         To add variable definitions to support semantic checks of connections, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_check_connects_defs"
         NOTE: Before creating this file, you must check the above list of declarations as well as the
"<expert>_ check_connects_defs" file for all other connector experts to see if the variable names you wish to declare are already in use, or the variables have already been declared. The generator does not check this for you. Any conflicts will cause the generator to produce a UniCon compiler that will contain compilation errors.
         The "<expert>_check_connects_defs" file should contain a code fragment, written in C language syntax, that declares the variables needed to support the other connector expert code fragments that get added to the "check_connects" routine (see sections 4.3.8.3 through 4.3.8.8 below).
         Indentation for the variable definitions should start at 4 spaces. Each line of the file must be terminated by a newline. The file may contain more than one variable definition, and each may be initialized with a value in the manner allowed by the C language definition.
         In our Socket example, we need to add the following variable definitions to support the connection checks in "check_connects." Our "socket_check_connects_defs" file looks like this:
         int socket;\n
[EOF]
         4.3.8.3. Initialization
         To add code to the beginning of the "check_connects" function to perform initialization for the rest of the code fragments, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_check_connects_initial"
         The contents of this file should simply be a code fragment, written in C language syntax, that performs initialization for the rest of the connector expert's code fragments that get added to the "check_connects" function.
         Indentation for the code fragment in the file should begin at 4 spaces, and every line in the file must be terminated by a newline.
         In our Socket example, we have no initialization code for the "check_connects" routine. Below is the code for the "rts" expert in the file "rts_check_connects_initial:"
         /*
* In the "check_connects" routine, we use the "rts_connectors" data structure
* in file "uni_rts.c"), so we call "uni_initialize_rtss" (also in "uni_rts.c") to initialize it.
*/

uni_initialize_rtss ();
[EOF]
         4.3.8.4. Per-Connection Initial Semantic Checks
         After initialization, the "check_connects" routine cycles through all of the connections in a composite implemention of a component. For each connection, the function performs some initialization and/or some initial semantic checks. To add per-connection initialization code or initial semantic checks for a connector expert, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_check_connects_per_connection_initial"
         The contents of this file should simply be a code fragment, written in C language syntax, that performs initialization and/or one or more initial semantic checks (per connection) in the "check_connects" function.
         Indentation for the code fragment in the file should begin at 8 spaces, and every line in the file must be terminated by a newline.
         In our Socket example, we have no per-connection initialization or semantic checking code for the
"check_connects" routine. Below is an excerpt from the code fragment for the "rts" expert in the file
"rts_check_connects_per_connection_initial:"
         /*
* Perform check #1 listed above (i.e., we check that every
* new RTScheduler connector uses a different processor).
*/

if (establish->use->u.p.conn->type == RTScheduler) {

found_it = error = 0;

/*
* Find the processor associated with this RTScheduler connector.
*/

processor = uni_find_processor (establish->use->av_pairs);

/*
* Check that no other RTScheduler connector we've encountered
* uses that same processor.
*/

loop_through_uni_list (rts_connectors, temp2, rts_conn, rts_conn_t *) {
/*
* First, if we've processed this connector instantiation
* already, make a note of it (increment the found_it
* variable).
*/

if (strcmp (rts_conn->conn_use->inst_name->string,
establish->use->inst_name->string) == 0) {
rts_connector = rts_conn;
found_it++;
}

/*
* Find the processor associated with this RTScheduler
* connector.
*/

temp_processor = uni_find_processor (rts_conn->conn_use->av_pairs);

/*
* If this processor is the same as the one for the establish
* we are currently checking (and it is *not* the same
* connector instantiation we are checking) report the error.
*/

if ((strcmp (temp_processor, processor) == 0) &&
(strcmp (rts_conn->conn_use->inst_name->string,
establish->use->inst_name->string) != 0)) {
sprintf (uni_err, uni_error_messages[RTSPRCER],
establish->use->inst_name->string,
rts_conn->conn_use->inst_name->string, processor);
uni_report_error (establish->lineno, uni_err, infile);
error++;
break;
}
}
<remaining code not shown>
}
[EOF]
         4.3.8.5. Per-Half-Connection Semantic Checks
         The word "half" in the above title is a slight misnomer. Connections do not always involve only two roles to which players must be connected exactly once. Some connections involve a single role to which more than one player (and often more than two players) must be connected. Therefore, a single connection can have more than two players connected to it. The word "half" is a convenient way to refer to a portion of a complete connection involving the connection of a single player to a single role in a connector. This section describes the addition of code fragments to "check_connects" that perform semantic checks on "half" connections.
         There are two database files for connector experts that can be used to add per-half-connection semantic checks to "check_connects:" "<expert>_check_connects_per_half_connection1" and
"<expert>_check_connects_per_half_connection2." The generator adds the code fragments to
"check_connects" from the first file in such a way that the semantic checks will only be made if the UniCon compiler determines that the attempted connection currently being examined is legal (i.e., that the types of the player and role in the connection are a legal match for the connector type). Otherwise, the connection is considered to be illegal and the semantic checks (from the first file) are not made. This design was implemented to prevent irrelevant error messages from being generated for connections which are illegal. The generator adds the code fragment from the second file in such a way that the semantic checks are unconditionally made.
         To add per-half-connection semantic checks for a connector expert that are made only if the connection is determined to be legal, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_check_connects_per_half_connection1"
         The contents of this file should simply be a code fragment, written in C language syntax, that performs semantic checks (per "half" connection) in the "check_connects" function. The semantic checks in this file should be designed with the invariant in mind that they will only be executed if the half connection currently being considered is determined to be legal. Indentation for the code fragment in the file should begin at 16 spaces, and every line in the file must be terminated by a newline.
         To add per-half-connection semantic checks for a connector expert that are made unconditionally, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_check_connects_per_half_connection2"
         The contents of this file should simply be a code fragment, written in C language syntax, that performs semantic checks (per "half" connection) in the "check_connects" function. Indentation for the code fragment in the file should begin at 8 spaces, and every line in the file must be terminated by a newline.
         For the Socket example we wish to override the user's specification of the MinConns and MaxConns attribute-value pairs for the Initiator and Responder roles; we want there to be exactly one connection to each role for an instantiated Socket connector. To support this, we must add a code fragment that checks the "connections" struct member of the "role" struct to see that no more than one connection has been made. This semantic check is irrelevant if the connection is not legal, so we add the code fragment to the first file. Our
"socket_check_connects_per_half_connection1" file looks like this:
         if (connect->rhs->use->u.p.conn->type == Socket) {
if (connect->rhs->u.role->connections > 1) {
sprintf (uni_err, uni_error_messages[MLTPIPCN],
connect->rhs->use->inst_name->string,
connect->rhs->u.role->name->string, "socket");
uni_report_error (connect->rhs->module->lineno, uni_err, infile);
}
}
[EOF]
         4.3.8.6. Signature Checks, Macro Redefines, and Name Matching
         The function "check_connects" performs semantic checking on signatures of players involved in a connection, and it performs semantic checking on the identifiers of the players in a connection to see that they match (if the connection is based on name matching). In addition, the function "generate_unique_names" creates the C languate macro definitions required to rename the identifiers in a connection if the "redefines" option is supplied on the command line during the UniCon compile session. Specifying the inclusion of one or more of the above mentioned features for a particular connector type is accomplished by adding certain parameters to the first line of the file "<expert>_signature_redefines_status," and possibly some code fragments following the first line to support the signature checking semantics.
         Signature Checking
         To turn on signature checking for a given connector type, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_signature_redefines_status"
         The first line of this file will contain up to five fields, separated by white space. To turn on signature checking the first field must contain the word "signature" (preferably in lowercase letters, but this is not necessary), and the second field must contain either of the two words "standard" or "nonstandard" (again, preferably in lowercase letters, but not necessary). The format and meaning of the code fragments added after the first line in this file will depend on the value in the second field of the first line.
         To turn off signature checking for a given connector type, simply do not include these two values in the file "<expert>_signature_redefines_status.".
         Standard Checking
         If the value of the second field on line one is "standard," then the generator creates signature checking code that is simply a call to the utility routine "uni_check_player_signature" that exists in the "uni_utility.c" file. The code fragments following the first line of the "<expert>_signature_redefines_status" file represent additions to the "uni_check_player_signature" routine to check the signatures of the players involved in the new type of connection.
         There are potentially two code fragments to be added to "<expert>_signature_redefines_status" for standard signature checking. The first is the local variable definitions that get added to the beginning of the routine, and the second is the semantic checks on the signatures. Each code fragment is preceded by a line with a keyword on it, signifying to the generator the purpose of the code fragment that follows it.
         If there are local variable definitions to be added to "uni_check_player_signature," then the file should contain the following:
         1. the word "definitions" (preferably in lowercase letters, but this is not necessary), preceded by a dollar sign character, `$', on a line by itself that is terminated by a newline (e.g., $definitions\n)
         2. a code fragment immediately following the line with "$definitions" on it, written in C language syntax, that defines local variables in the function "uni_check_player_signature" to support the code fragment that will be actually checking the signatures
         The following variable definitions are automatically added to "uni_check_player_signature" by the generator:
         string_t *parm1, *parm2;
uni_node temp1, temp2;
int parmno = 0, error = 0;
player_t *connect_p, *definer_p;
char *cmember, *dmember, *member_pattern = "member '%s' of ";
char *match, *conn_type, *conn_name;
         NOTE: Before creating this code fragment, you must check the above list of declarations as well as the
"<expert>_ signature_redefines_status" file for all other connector experts to see if the variable names you wish to add are already in use, or the variables have already been declared. The generator does not check this for you. Any conflicts will cause the generator to produce a UniCon compiler that will contain compilation errors.
         Indentation for the variable definitions should begin at 4 spaces, and each line in the code fragment must be terminated by a newline.
         To add signature checking semantic checks to "uni_check_player_signature," the file should contain the following:
         1. the word "code" (preferably in lowercase letters, but this is not necessary), preceded by a dollar sign character, `$', on a line by itself that is terminated by a newline (e.g., $code\n)
         2. a code fragment immediately following the line with "$code" on it, written in C language syntax, that checks the signatures of the two players in the connection
         Indentation for the code fragment should begin at 12 spaces, and each line in the code fragment must be terminated by a newline.
         In our Socket example, we wish to employ standard signature checking.
Our "socket_signature_redefines_status" file looks like this:
         signature standard
$definitions
$code
/*
* Checking signatures for Socket connections
* requires checking only the return type strings.
*/

if (((connect_p->type == StreamIn) &&
(definer_p->type == StreamOut)) ||
((connect_p->type == StreamOut) &&
(definer_p->type == StreamIn))) {
if (strcmp (uni_lower_case (connect_p->Class.Stream.signature->
return_type->string),
uni_lower_case (definer_p->Class.Stream.signature->
return_type->string)) != 0) {
if (!suppress) {
uni_get_member_messages (connect_p, connect,
member_pattern, &cmember,
UNI_NULL_STRING, definer_p,
definer, member_pattern,
&dmember, UNI_NULL_STRING);
uni_get_match_message (establish, &match, &conn_type,
&conn_name);
sprintf (uni_err, uni_error_messages[SIGNERRS],
cmember, connect->use->inst_name->string,
connect->u.player->name->string,
dmember, definer->use->inst_name->string,
definer->u.player->name->string,
match, conn_type, conn_name);
uni_report_error (connect->module->lineno,
uni_err, infile);
}
error++;
}
}

[EOF]
         Note that if there are no definitions to be added, we may or may not include the line with the "$definitions" on it in the file. Either way has no effect.
         Nonstandard Checking
         If the value of the second field on line one is "nonstandard," then the connector designer must supply a new signature checking function, specific to the new connector type, to be added to "uni_utility.c" (see section 4.5.2 below). The code fragments following the first line of the "<expert>_signature_redefines_status" file represent preparation for a call to this function as well as the call itself.
         There are potentially two code fragments to be added to "<expert>_signature_redefines_status" for nonstandard signature checking. The first is the code fragment that gets executed once in preparation for checking the signatures of all the players in the connection. The second is the code fragment that calls the new connector-specific signature checking function with the correct parameters for each player in the connection. Each code fragment is preceded by a line with a keyword on it, signifying to the generator the purpose of the code fragment that follows it.
         If there is preparation code that gets added to "check_connects" to be executed once prior to the call to the connector-specific signature checking function, then the file should contain the following:
         1. the word "setup" (preferably in lowercase letters, but this is not necessary), preceded by a dollar sign character, `$', on a line by itself that is terminated by a newline (e.g., $setup\n)
         2. a code fragment immediately following the line with "$setup" on it, written in C language syntax, that gets executed once to prepare for the call to the connector-specific signature checking function
         Indentation for the code fragment should begin at 12 spaces, and each line in the code fragment must be terminated by a newline.
         The file should contain the following for the call to the connector-specific signature checking function:
         1. the word "call" (preferably in lowercase letters, but this is not necessary), preceded by a dollar sign character, `$', on a line by itself that is terminated by a newline (e.g., $call\n)
         2. a code fragment immediately following the line with "$call" on it, written in C language syntax, that makes the call to the connector-specific signature checking function
         Indentation for the code fragment should begin at 20 spaces, and each line in the code fragment must be terminated by a newline.
         Our Socket example contains standard signature checking, so below is an example of the nonstandard signature checking that exists in the UniCon compiler for the FileIO connector. The
"fileio_signature_redefines_status" file looks like this:
         signature nonstandard
$setup
recordfmt = find_recordformat (establish, &seqfile);
$call
if (connect->lhs->use->u.i.comp->type != SeqFile)
uni_check_fileio_signature (establish, connect->lhs,
recordfmt, seqfile, infile);
[EOF]
         In a FileIO connection, the value of the RecordFormat attribute of the SeqFile component contains the signature that the rest of the players in the connection must be checked agains. Before checking the signature of any of the players in the connection, we must find the recordformat attribute. Then, when checking each player in the connection, we pass the value of the RecordFormat attribute along with the player to the FileIO signature checking function.
         Macro Redefines
         To get the function "generate_unique_names" to generate the C language macro definitions required to rename the players in a connection of the type being added, two values must be added to the first line of the file "<expert_signature_redefines_status." If signature checking is not turned on for the connector, then this file will not exist so it must be created, and the two values must be added to fields one and two of the first line. If signature checking is turned on, then the file will already exist and fields one and two will already be occupied. In this case, add the two new values to fields three and four of the first line.
         The first value is the word "redefines" (preferably in lowercase letters, but this is not required). The second value is the enumeration value for the "definer" role of the connector (as it appears in the file
"<expert_role_enums").
         The first value tells the generator to create code that will rename the players in the new connection type if the "redefines" option is supplied at compile time to the UniCon compiler. The second value, the "definer" role enumeration, is needed by the generator to generate code to support the renaming feature.
         In our Socket example, we do not wish to turn on the renaming option, so at this point the contents of the "socket_signature_redefines_status" file are exactly as specified above (see Standard Checking). Below is an excerpt from the file "datause_signature_redefines_status" of the DataAccess connector:
         signature standard redefines Definer namematching
$definitions
$code
/*
* Checking signatures for DataAccess connections involves only
* checking the return type strings.
*/

if (((connect_p->type == GlobalDataUse) &&
(definer_p->type == GlobalDataDef)) ||
((connect_p->type == GlobalDataDef) &&
(definer_p->type == GlobalDataUse))) {
<code fragment ommitted>
}

[EOF]
         If hypothetically we were to turn off signature checking for the DataAccess connector, the
"datause_signature_redefines_status" file would look as follows:
         redefines Definer namematching
[EOF]
         The "namematching" value is described below.
         Name Matching
         Name matching is an attribute (i.e., a characteristic) of a connector type. For example, procedure call, data access, and remote procedure call connections are all satisfied via name matching. The linker (which actually performs the connections in the implementation) will not create a connection unless it can resolve definitions and calls/uses via name matching.
         Not all connectors have the name matching characterstic. It is necessary for the generator to know whether a connector is based on name matching so that it can check to see if the names of the players in the connection are identical.
         Specifying the value "namematching" in the "<expert>_signature_redefines_status" file instructs the generator to add the name matching semantic checks. The value "namematching" (preferably in lowercase letters, but this is not necessary) gets added to the first line of the file. It is placed in the fifth field if both the signature checking and redefines features are turned on. It is placed in the third field if only the redefines or signature checking features is turned on. It is placed in the first field if neither feature is turned on.
         In our Socket example, the Socket connector is not based on name matching so this value is ommitted from the "socket_signature_redefines_status" file.
         4.3.8.7. Per-Connection Final Semantic Checks
         For each connection, the "check_connects" function can perform some final semantic checks. To add some final per-connection semantic checks for a connector expert, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_check_connects_per_connection_final"
         The contents of this file should simply be a code fragment, written in C language syntax, that performs one or more final semantic checks (per connection) in the "check_connects" function. These semantic checks get performed once for each complete connection, following the semantic checks on the "half" connections (see section 4.3.9.5 above).
         Indentation for the code fragment in the file should begin at 8 spaces, and every line in the file must be terminated by a newline.
         In our Socket example, we have no per-connection final semantic checking code for the
"check_connects" routine. Below is an excerpt from the code fragment for the "pipe" expert in the file
"pipe_check_connects_per_connection_initial." The code fragment checks to make sure that the connection is not attempting to connect two files:
         /*
* If we are attempting to connect more than 1 file with a Pipe,
* report the error.
*/

if (pipe && (files > 1)) {
sprintf (uni_err, uni_error_messages[TWOFILER],
establish->use->inst_name->string);
uni_report_error (establish->lineno, uni_err, infile);
}

[EOF]
         In the code fragment above, the "pipe" integer variable was declared in the code fragment in the file "pipe_check_connects_defs," and it was set in the code fragment in the file
"pipe_check_connects_per_connection_initial." The "files" integer variable was set in the code fragment in the file "pipe_check_connects_per_half_connection2."
         4.3.8.8. Final Semantic Checks
         There may be a need for a final set of semantic checks that get performed on the connections in a composite implementation as a whole. If this is the case, such semantic checks get added via the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_check_connects_final"
         The contents of this file should simply be a code fragment, written in C language syntax, that performs one or more final semantic checks in the "check_connects" function. These semantic checks get performed once, at the end of the "check_connects" function.
         Indentation for the code fragment in the file should begin at 4 spaces, and every line in the file must be terminated by a newline.
         In our Socket example, we have no per-connection final semantic checking code for the
"check_connects" routine. Below is an excerpt from the code fragment for the "rts" expert in the file
"rts_check_connects_final." The code fragment checks to see that all SchedProcess components in a composite implementation are connected to some RTScheduler connector. This can only be done after all connections in a composite implementation have been checked:
         /*
* Cycle through all the instantiations for this component.
*/

loop_through_uni_list (comp->impl->uses, temp, use, use_t *) {

/*
* We are only interested in SchedProcess components.
*/

if (use->type == Protocol)
continue;
if (use->u.i.comp->type != SchedProcess)
continue;

/*
* Cycle through all the RTScheduler connectors encountered so far,
* and look through the component instantiations hooked to each of
* them. The current SchedProcess connector we are checking *must*
* be hooked to one of these connectors.
*/

found_it = 0;
loop_through_uni_list (rts_connectors, temp2, rts_conn, rts_conn_t *) {
loop_through_uni_list (rts_conn->comp_uses, temp3, tuse, use_t *) {
if (strcmp (use->inst_name->string,
tuse->inst_name->string) == 0) {
found_it++;
break;
}
}
if (found_it)
break;
}

/*
* If the SchedProcess is not hooked to any RTScheduler connector,
* report the error.
*/

if (!found_it) {
sprintf (uni_err, uni_error_messages[SPNOCNTD],
use->inst_name->string);
uni_report_error (use->lineno, uni_err, infile);
}
}

[EOF]
         4.3.9. Automatic Connections
         The UniCon compiler is capable of making automatic connections using the builtin connector definitions that the connector expert designer adds (see section 4.3.1 above). In fact, the generator will not add the automatic connection code that a connector expert has in its database subdirectory files unless the file "<expert>_builtin_connector" is present in the database subdirectory as well. The following three subsections describe how to add code to support automatic connections for the new connector expert.
         4.3.9.1. Automatic Connection Function Definitions
         To add functions to the semantic analyzer that will perform automatic connections of unconnected players of the appropriate types, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_auto_connect_routine_def"
         This file should contain the function definition, in C language syntax, for a function that will automatically connect two unconnected players of the appropriate types in a composite implementation. The function should be declared to be "static." The file may also contain function definitions for support routines that the function itself needs to perform its task. The function definition may have any name and any signature, and indentation of the code fragment should start at 0 spaces. Each line in the file must be terminated by a newline.
         The context for the call to this function is as follows. After all syntax and semantic checks have been made, and before any unconnected or unbound players in a composite implementation are reported, the compiler cycles through the list of players in each component instantiation inside a composite implementation to see if any are unconnected. For each unconnected player, the compiler will call a function (based on the player's type) to attempt to automatically find a matching player for it in the implementation and connect to it. So the context is that the function will be called once for each unconnected player of the appropriate type that is found in a component instantiation. For example, if unconnected StreamIn and StreamOut players are found, the compiler calls the "resolve_unconnected_stream" function of the pipe connector expert once for each unconnected player encountered.
         NOTE: The function definitions for every function in this file must be written to be compatible with ANSI C and K&R C simultaneously. The following is an example of such a function definition:
         static int resolve_unconnected_call

#ifdef __STDC__
(impl_t *impl, use_t *use, player_t *player)
#else
(impl, use, player)
impl_t *impl;
use_t *use;
player_t *player;
#endif

{
Search through the internal establish records to find a connector where:
1. the Definer role is unconnected
2. the Definer role is connected but not max'd out
(settle for #2 above only if #1 does not yield a match)
and where the players involved in the connection have the same
name as the name of the player we are trying to connect and the
complementary player type
If a match is found, create a connection to it and perform a semantic check
on the signatures of the two players - then return "success"
If no match is found, search through all the player definitions in the instantiation for
an unconnected one that has a complementary type to the one we are
trying to connect, and the same name
If a match is found, create a connection to it and perform a semantic check
on the signatures of the two players - then return "success"
Return "fail"
}
[EOF]

         In our Socket example, we have no automatic connection function because we do not wish to arbitrarily choose for the system designer the Initiator and Responder roles to connect to, since the compiler's choices would be arbitrary (unlike the case where the connector is based on name matching, where confidence is higher that the right match is being made). The above code fragment is an excerpt from the automatic connection function supplied for the "proccall" connector expert. The body of the definition contains the algorithm rather than the exact implementation, for brevity.
         Reuse of Proccall Automatic Connection Function
         The automatic connection function "resolve_unconnected_call" for the proccall connector expert is written in such a way that it can be used for any connector that makes connections based on name matching. So far in the UniCon compiler this applies to three connector experts: proccall, rproccall, and datause. Each of these connector experts can effectively use the same automatic connection routine.
         To make it possible to share automatic connection routines, the file "<expert>_auto_connect_routine_def" may contain a "pointer" to another expert's function definition rather than supply its own. The format of this "pointer" is as follows. The file must contain only one line, terminated by a newline. The line must consist entirely of the "pointer" to the other expert's routine definition. The "pointer" consists of a single identifier that is surrounded by parentheses and preceded by a dollar sign, "$". It is recommended that the identifier be in lowercase letters, but this is not required. The identifier must be the name of the expert whose automatic connection function is being used.
         The contents of the files "datause_auto_connect_routine_def" and "rproccall_auto_connect_routine_def" both look like this:
         $(proccall)
[EOF]
         In general, this is the way to reuse the automatic connection function of any given expert. However, the automatic connection function of the proccall expert has some connector specific code in it that must be present if other connectors are going to use it. This code fragment and the process for including it in the automatic connection function definition for each expert using the proccall expert's function are described in section 4.3.10.2 below.
         4.3.9.2. Connector Specific Code Fragment
         Connector specific code fragments can be added to an automatic connection function of another expert, but only in one place in that function (right now the generator is no more sophisticated than that; if it becomes important in the future, the generator can be enhanced).
         For example, the rproccall and datause connector experts use the automatic connection function of the proccall expert, and an rproccall and datause code fragment must be inserted into the proccall function. So, in the proccall automatic connection function definition there is a place holder (or "marker") that marks where the connector specific code must go. This marker can be included in any automatic connection function that will be shared and in which connector specific code fragments must be inserted. This marker has a specific form. It must be on a line by itself that is terminated by a newline, surrounded by parentheses, and preceded by a dollar sign character, "$". It is recommended that the marker be in lowercase letters, but this is not required. The marker must be the word "connector_specific_cases." The entire line containing the marker will be repaced with all of the connector specific code fragments that are to be included in the automatic connection function.
         It is recommended that all of the connector specific code fragments have the same form. For example, the marker might be placed inside a "switch" statement in the automatic connection function, and the connector specific code fragments would be "case" statements that get placed inside the "switch" statement. Alternatively, the marker might be placed where any C language statement might go, and the connector specific code fragments could each consist of an "if" statement that checks the connector type and performs connector specific processing. For the proccall automatic connection function, the connector specific code fragments are "case" statements that get placed inside a "switch" statement.
         The connector specific code fragments must reside in the following file in the connector expert's database subdirectory:
         "<expert>_auto_connect_case_specific."
         The contents of this file are as follows. The file must contain a code fragment written in C language syntax that performs connector specific processing in a shared automatic connection function.
         The format of this file for the experts that use the proccall expert's automatic connection function is as follows. The code fragment must be a "case" statement. The last statement of the fragment must be a "break" statement that is followed by a blank line. Indentation of the fragment should start at 12 spaces, and each line of code must be terminated by a newline.
         The fragment itself must have the following form. The constant expression of the "case" statement must be the UniCon type name of the player type that gets connected to the Caller or User role of the connector. For example, for the rproccall expert the constant expression must be RPCCall. For the datause expert it must be GlobalDataUse. The code fragment in the "case" statement is simple. It consists of a simple "if" statement. The "if" statement must check the type of the connector. If the connector type is not of the type of the expert (e.g., if the connector expert is rproccall, the type must be RemoteProcCall), then the "continue" statement must be executed.
         Below is the example code fragment for the rproccall connector expert that gets included in the proccall expert's automatic connection function:
         case RPCCall:

/*
* If we're connecting a player of type RPCCall, we only
* want to look at connectors of type RemoteProcCall.
*/

if (establish->use->u.p.conn->type != RemoteProcCall)
continue;
break;

[EOF]
         4.3.9.3. Function Call
         To add the code fragment that makes the call to the automatic connection function that was added as described in section 4.3.10.1 above, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_auto_connect_routine_call"
         This file should contain the call to the function defined via the file "<expert>_auto_connect_routine_def" for the connector expert. It should be written in C language syntax, and the call must match the exact name of the routine as well as the exact names and number of arguments in the argument list as declared in the function definition. The indentation of the code fragment should start at 24 spaces. Each line in the file must be terminated by a newline.
         The code fragment has a format that must be strictly adhered to. The code fragment must be a "case" statement. The constant expression of the "case" statement must be the UniCon type name of the player type that gets connected to the Caller or User role of the connector. The code fragment must end with the "break" statement that is followed by a blank line.
         The call to the automatic connection function is usually embedded in an "if" statement in a way that implements the specific semantics of the automatic connection process for the specific connector expert. In general, the same player can be connected more than once (in theory). Each player has a MinAssocs value which dictates a requirement for the number of times that a player must be connected. It also has a "connections" attribute which describes the number of times the system designer has made explicit connections to it. If the number of connections is less than the requirement, the player must be automatically connected the number of times dictated by the difference between these values. This number of automatic connections is stored in a variable named "matches_to_be_found" that is global to the code fragment making the automatic connection function call.
         The automatic connection function should be written to return an integer indicating whether or not an automatic connection was made for the given player. If a connection was made, the function should return "true" (i.e., the integer value "1"); otherwise it should return the value "false" (i.e., the integer value "0"). In the calling code fragment, the condition of the "if" statement should make the function call and check the return value. If the return value is "false," then no more automatic connections should be attempted and the
"matches_to_be_found" variable should be set to "0" to exit the loop in which the calling code fragment exists. If the return value is "true," then the "matches_to_be_found" variable should be simply decremented so that the loop may continue with the next iteration to attempt another automatic connection. When the
"matches_to_be_found" variable is decremented to 0, the loop is exited.
         The rproccall, proccall, and datause connector experts all use the above algorithm as their calling code fragment. The calling code fragment for the pipe connector expert is somewhat different. Pipe connections can have only one player connected to each role, regardless of the MinAssocs value specified. So the calling code fragment for the pipe expert only attempts one automatic connection. It then sets the "matches_to_be_found" variable to 0.
         Below is the calling code fragment for the proccall expert that is contained in the file
"proccall_auto_connect_routine_call:"
         case RoutineCall:

/*
* If the player is of type RoutineCall, then call
* "resolve_unconnected_call" to try to make a
* connection. do this as many times as it takes.
*/

if (resolve_unconnected_call (comp->impl, use, player))
matches_to_be_found--;
else
matches_to_be_found = 0;

break;

[EOF]
         Below is the calling code fragment for the pipe expert that is contained in the file
"pipe_auto_connect_routine_call:"
         case StreamIn:
case StreamOut:

/*
* If the player is of type StreamIn or StreamOut, call
* "resolve_unconnected_stream" to try to make a
* connection. Do this only once, then.
*/

if (!(player->connections))
resolve_unconnected_stream (cuse, comp, use, player);

matches_to_be_found = 0;
break;

[EOF]
         4.3.10. Connector-Specific Analyses
         Although analysis tools are most often developed independently of the model of the UniCon language, some of the tools get applied to connections of specific types. Therefore, the generator allows for the incorporation of analysis tools as part of the expertise of a given connector type. The UniCon compiler has a function in the semantic analyzer called "perform_analyses" from which analysis tools are invoked during the semantic analysis phase of a compilation.
         The "perform_analyses" function cycles through the connections in a composite implementation of a component and invokes analysis tools on individual connections where appropriate. To date, there has been only one analysis tool incorporated into the UniCon compiler. It is the Rate Monotonic Analysis (RMA) tool, and the analysis is applied to RTScheduler connections. The "perform_analyses" function checks the type of each connection in the composite implementation, and invokes the RMA analysis tool when it encounters connections of type RTScheduler that use the "rate monotonic" scheduling algorithm. The body of this function consists of a "switch" statement inside a loop. The loop looks at each connection in the composite implementation, and the switch statement checks the type of the connection. The code fragment in each "case" statement consists of code that invokes the analysis tool.
         To add a code fragment that invokes an analysis tool on a connection of a specific type, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_perform_analyses"
         The contents of this file are as follows. The file must contain a code fragment written in C language syntax that performs an analysis, invokes an analysis tool, or calls a function to perform an analysis (or invoke an analysis tool) on a connection of a particular type.
         The format of the code fragment in this file is as follows. The code fragment must be the contents of a "case" statement (i.e., the actual "case" statement that precedes the fragment and the "break" statement that follows are created by the generator and are not to be supplied as part of the code fragment itself). Indentation of the fragment should start at 16 spaces, and each line of code must be terminated by a newline.
         In our Socket example, there is no analysis performed on Socket connections. The code fragment below is taken from the rts connector expert, which performs a rate monotonic analysis on RTScheduler connections when the algorithm is the "rate monotonic" scheduling algorithm.
         if (uni_find_algorithm (use->av_pairs) != Rate_Monotonic)
continue;

/*
* The "uni_perform_RMA" routine exists in the file "uni_rts.c".
*/

uni_perform_RMA (use);

[EOF]
         The "uni_perform_RMA" function that invokes the RMA tool is part of the rts connector expert's backend processing code and exists in the file "uni_rts.c." This file is described in greater detail in section 4.4 below.
         4.4. Build Phase
         The build phase of the UniCon compiler is essentially the backend "code generator." The attributed syntax tree that is constructed in the syntax and semantics phases is traversed by the compiler's "uni_builder" function to generate any "glue code" required to fuse the component implementations together in the final system. The backend also creates the odin scripts necessary for building the executable files for the system, and then executes odin to build them.
         4.4.1. Connector Backend Expertise
         UniCon is not a traditional compiler in the sense that it does not simply produce a machine code translation of the UniCon input. The model for the UniCon compiler is to map the collection of UniCon component abstractions into groups that represent Unix or Mach processes, generate "glue code" necessary to support both the connections between the processes and the fusion of the file system objects into executables, and then generate the construction instructions for odin to actually build the executables.
         The model for the UniCon compiler backend requires that each connector expert contribute to the mapping of UniCon component abstractions to implementation entities and to the generation of "glue code" construction instructions. To facilitate this, each connector expert maintains a set of data structures that it populates with information representing this mapping. The connector expert populates the data structures by walking the attributed syntax tree and translating the abstractions in the tree to architectural entities in the implementation. For some experts such as the pipe connector expert, this involves creating a flat, two-dimensional representation of the topology of a pipe-and-filter system. For other experts such as the proccall connector expert, this involves simply grouping source and object files into groups that represent executables.
         Once the data structures have been populated, each connector expert walks the data structures to generate any "glue code" and odin instructions for building executables from the source files in the system.
         The algorithm that the UniCon compiler uses for "code generation" is as follows. The compiler cycles through all of the components in a system. For each component with a composite implementation, it then cycles through all of the connections, and based on the type of the connection it calls the function
"uni_create_<expert>_connection" to populate the data structures of the given connector expert with the appropriate information. After all the connections in a composite implementation have been processed in this way, the compiler calls the "uni_process_<expert>s" function for all the experts to actually generate the "glue code" and odin instructions necessary to support the connections in the implementation of the system.
         <expert>.cc and <expert>.hh
         To create connector backend expertise for an expert, create the following two files in the connector expert's database subdirectory (they are required):
         <expert>.hh
<expert>.cc
         The <expert>.hh file must declare C language typedefs for the data types used to implement the data structures described in the above section for each expert. The following is the typedef for the rproccall connector expert that implements its data structure:
         typedef struct {
use_t *module;
char *mig_file;
uni_list decl_players; /* list of (qname_t *) players */
uni_list decl_protocols; /* list of (use_t *) */
uni_list call_modules; /* list of (rpc_t *) */
uni_list call_protocols; /* list of (use_t *) */
int processed; /* for generating process graph */
} rpc_t;
         This file must also define C language prototypes for the three functions described in the following paragraph. These function prototypes must be declared to be compatible with both ANSI and K&R C. For example, the following prototype is declared in this way:
         void uni_process_rproccalls (
#ifdef __STDC__
impl_t *impl
#endif
);

         The <expert>.cc file must globally define three functions that are invoked by the compiler's "uni_builder" function. These functions must have the following signatures:
         void uni_initialize_<expert>s (
#ifdef __STDC__
void
#endif
);


void uni_create_<expert>_connection (
#ifdef __STDC__
qname_t *lhs,
qname_t *rhs
#endif
);

void uni_process_<expert>s (
#ifdef __STDC__
impl_t *impl
#endif
);
         The "uni_process_<expert>s" function may require additional arguments. For example, the pipe expert's "uni_process_pipes" function requires two additional arguments:
         void uni_process_pipes (
#ifdef __STDC__
char *inst_name,
impl_t *impl,
uni_list sources
#endif
);
         The "uni_initialize_<expert>s" function is called by the "uni_builder" function at the start of the processing for each component with a composite implementation. This function cleans up the expert's data structures from the previous composite component, and reinitializes them for the current component.
         The "uni_create_<expert>_connection" routine is called by the "uni_builder" function as the builder cycles through each connection in the composite implementation. For each "half" connection, the builder calls the function to add the appropriate connection information to the expert's data structures.
         The "uni_process_<expert>s function is called once for each expert after all connections in a composite component have been processed. This routine walks the expert's data structures and generates any "glue code" and odin instructions that the builder needs to create the executables for the system.
         The design of the expert's data structures as well as the design of the processing algorithms in the three functions described above are specific to each connector type and are beyond the scope of this user manual.
         4.4.2. Processing Connector Data Structures
         The generator creates calls to the three functions for each expert mentioned in section 4.4.1 in the compiler's "uni_builder.c" source file. The calls to the first two functions can be generated automatically since the signatures for each of these are identical for each expert. The call to the third function must be specified by the designer of the connector expert since the signature is nonstandard.
         To add the code fragment that makes the call to the "uni_process_<expert>s" function (described in section 4.4.1 above), create the following file in the connector expert's database subdirectory (it is required):
         "<expert>_process_connector_call"
         The code fragment in this file should be written in C language syntax. Indentation of the fragment should start at 4 spaces, and each line in the code fragment must be terminated by a newline.
         Call Ordering
         There may be a situation where one connector expert has a dependency on another such that the processing of one connector expert's data structures must occur after the processing of another's. This implies that there may be an ordering requirement for the calls to the "uni_process_<expert>s" functions in the "uni_builder" function. This is, in fact, true for the rproccall connector expert. The call to "uni_process_rproccalls" must be made after the call to "uni_process_rtss." To accommodate this ordering, the generator allows a simple specification of ordering to be placed on the first line of the "<expert>_process_connector_call" file.
         The ordering specification must be placed on the first line of the file, and that line must not contain any other text. The ordering specification must begin with a dollar sign and be surrounded by parentheses and terminated by a newline. The specification itself consists of two words separated by white space (preferably in lowercase letters, but not required). The first is the word "after," and the second is the name of the connector expert whose call to "uni_process_<expert>s" the currently generated call will follow.
         As an example, below are the contents of the rproccall connector's file "rproccall_process_connector_call:"
         $(after rts)
uni_process_rproccalls (impl);
[EOF]
         In the "uni_builder" function, the above call will be placed after the call to the function "uni_process_rtss." The generator can determine when there are cyclic dependencies. It will report such dependencies and abort, thereby preventing the generation of erroneous code in the compiler.
         4.4.3. Expert Support Definitions
         There may be a situation where one connector expert has a dependency on a second expert such that if the second expert is included in a compiler configuration then the first expert requires additional function definitions to be added to its "uni_<expert>.c" file (with corresponding function prototypes added to its "uni_<expert>.h" file). This section describes how to add these extra support functions for a particular expert.
         NOTE: The assumption here is that these extra support functions are considered to be part of one connector's expertise (requiring them to reside in "uni_<expert>.c") yet will be called from the C code in another expert's "uni_<expert>.c" file. This section essentially describes a code reduction feature. There is no harm in always including these types of function definitions, but if there are many such definitions in the compiler's connector expertise, removing them in this way can reduce the size of the compiler's binary executable file. The generator provides this feature to include only those function definitions that are actually needed.
         Function Prototypes
         As is the case with the extra support functions for "uni_semantic.c" described in sections 4.2.4.3 and 4.3.2 above, the extra support functions for the connector experts can be included in the presence or absence of a particular attribute-value pair or connector expert. This is described in greater detail below.
         To add functions prototypes to "uni_<expert>.h" (we describe the addition of the functions themselves in the section below), create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_expert_support_defs"
         For prototypes that get included in the presence of a particular attribute-value pair or connector expert, the "<expert>_expert_support_defs" file must contain the following:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums") or the name of the connector expert (as it appears in the file "<expert>_connector_enum"), preceded by a dollar sign character, `$', followed by white space and then the word "prototypes" (preferably in lowercase letters, but this is not required), followed by a newline (e.g., $Protocol prototypes\n)
         2. a function prototype declaration that follows the line with the `$<attribute name>' on it, using C language syntax declared to be both ANSI C and K&R C compatible, as follows:

int function_prototype_name (
#ifdef __STDC__
arguments
#endif
);
         For prototypes that get included in the absence of a particular attribute-value pair or connector expert, insert the word "no" between the dollar sign and attribute or connector name in instruction step number one (e.g., $noProtocol). There should be no white space between the word "no" and the dollar-sign and attribute or connector name. It is recommended that the word "no" contain only lowercase letters, although this is not necessary. For prototypes that get included unconditionally, substitute the "__always__" marker in place of the attribute name in instruction step number one.
         Indentation in the file should start at 0 spaces. Each line in the file "<expert>_expert_support_defs" must be terminated by a newline.
         In our Socket example, assume that the socket expert has a function called "uni_pipe_is_socket" to handle turning a pipe into a socket if the pipe connects filters across platforms in a pipe-and-filter system. This function is only needed when the pipe expert is present in the compiler, even though it belongs with the socket expert. Therefore, we add it to the socket expert only if the pipe expert is present. The contents of our
"socket_expert_support_defs" file look like the following (note that our global function definition's name starts with "uni_" because it it globally declared) for the prototype declaration:
         $Pipe prototypes\n
int uni_pipe_is_socket (
#ifdef __STDC__
impl_t *impl
#endif
);
[EOF]
         Multiple function prototypes can be grouped under a single attribute-value pair or connector marker in the file.
         Function Definitions
         To add the extra support function definitions to the "uni_<expert>.c" file, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_expert_support_defs"
         This file may have already been created to add the corresponding function prototypes (see section Function Prototypes above). If so, then simply add to the existing file. The format for the function definitions in this file is identical to the format of the prototypes.
         For functions that get included in the presence of a particular attribute-value pair or connector expert, the
"<expert>_expert_support_defs" file must contain the following:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums") or the name of the connector expert (as it appears in the file "<expert>_connector_enum"), preceded by a dollar sign character, `$', followed by white space and then the word "definitions" (preferably in lowercase letters, but this is not required), followed by a newline (e.g., $Protocol definitions\n)
         2. a static or global function definition that follows the line with the `$<attribute name>' on it, using C language syntax declared to be both ANSI C and K&R C compatible, as follows:

int function_name (

#ifdef __STDC__
(arguments)
#else
(argument names)
argument declarations
#endif
{
<specific code goes here>
}
         For functions that get included in the absence of a particular attribute-value pair or connector expert, insert the word "no" between the dollar sign and attribute or connector name in instruction step number one (e.g., $noProtocol). There should be no white space between the word "no" and the dollar-sign and attribute or connector name. It is recommended that the word "no" contain only lowercase letters, although this is not necessary. For prototypes that get included unconditionally, substitute the "__always__" marker in place of the attribute name in instruction step number one.
         Indentation in the file should start at 0 spaces. Each line in the file "<expert>_expert_support_defs" must be terminated by a newline.
         In our Socket example, assume we now wish to define the function "uni_pipe_is_socket" whose prototype we've added in the previous section:
         < previously added support definitions and prototypes >
$Pipe definitions\n
int uni_pipe_is_socket

#ifdef __STDC__
(impl_t *impl)
#else
(impl)
impl_t *impl;
#endif

{
<specific code goes here>
}
[EOF]
         Multiple functions can be grouped under a single attribute-value pair or connector marker in the file.
         4.5. Main and Utilities
         In the UniCon compiler, any function that is called from more than one module (i.e., C source file) is considered to be a "utility" function and gets placed in the file "uni_utility.c". Section 4.5.2 describes how to add utility functions to this file. Section 4.5.1 describes how to add code fragments to the "uni_duplicate_av_pair" routine which resides in the "uni_utility.c" source file.
         4.5.1. Duplicate Attribute-Value Pairs
         There is a function in "uni_utility.h" called "uni_duplicate_av_pair." This function is responsible for determining if a particular attribute-value pair specification in a list is a duplicate. Usually an attribute is a duplicate simply if it is specified more than once in the same list. For some attributes, however, more than one specification in a list is allowed, and a there are other rules governing whether or not it is a duplicate (e.g., the value in the attribute-value pair is identical to the value in a previous attribute-value pair in the same list). In this case "uni_duplicate_av_pair" must be modified to be able to determine when such an attribute is a duplicate. A code fragment must be inserted into "uni_duplicate_av_pair" for each attribute that requires an algorithm to determine if it is a duplicate.
         The code fragment can either check for the duplicate itself, or can call a function to do it. In the latter case, the function would then be added to "uni_utility.c" as described below in section 4.5.2 The latter case is recommended. The code fragment can either be added in the absence or presence of an attribute-value pair.
         To add code fragments to "uni_duplicate_av_pair" to check for duplicate attribute-value pairs, create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_utility_duplicate_av_pair"
         This file must contain the following:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums"), preceded by a dollar sign character, `$', followed by white space and then the word "definitions" (preferably in lowercase letters, but this is not required), followed by a newline (e.g., $Protocol definitions\n)
         2. a code fragment, using C language syntax that implements a check to see that the attribute value pair is a duplicate - if it is, then the integer variable "duplicate" must be set to a non-zero value
         For functions that get included in the absence of a particular attribute-value pair, insert the word "no" between the dollar sign and attribute name in instruction step number one (e.g., $noProtocol). There should be no white space between the word "no" and the dollar-sign and attribute name. It is recommended that the word "no" contain only lowercase letters, although this is not necessary. For prototypes that get included unconditionally, substitute the "__always__" marker in place of the attribute name in instruction step number one.
         Indentation in the file should start at 16 spaces. Each line in the file "<expert>_utility_duplicate_av_pair" must be terminated by a newline.
         In our Socket example we do not have a sophisticated enough attribute-value pair to use as an example. So, below is an example taken from the "bundler" connector expert:
         $Match
/*
* Is the Match attribute a duplicate?
*/

if (tav_pair2->attribute == Match)
duplicate = uni_is_duplicate_Match (tav_pair2, tav_pair);

[EOF]
         Notice that this code fragment simply calls another "utility" function to determine whether or not it is a duplicate.
         4.5.2. Utility Support Definitions
         Utility support definitions are added in the same way that the semantic support definitions (see sections 4.2.4.3 and 4.3.2) and the expert support definitions (see section 4.4.3) are added. They can be added in the presence of absence of a particular attribute-value pair or connector expert.
         Function Prototypes
         To add functions prototypes to "uni_utility.h" (we describe the addition of the functions themselves in the section below), create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_utility_support_defs"
         For prototypes that get included in the presence of a particular attribute-value pair or connector expert, the "<expert>_utility_support_defs" file must contain the following:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums") or the name of the connector expert (as it appears in the file "<expert>_connector_enum"), preceded by a dollar sign character, `$', followed by white space and then the word "prototypes" (preferably in lowercase letters, but this is not required), followed by a newline (e.g., $Protocol prototypes\n)
         2. a function prototype declaration that follows the line with the `$<attribute name>' on it, using C language syntax declared to be both ANSI C and K&R C compatible, as follows:

int function_prototype_name (
#ifdef __STDC__
arguments
#endif
);
         For prototypes that get included in the absence of a particular attribute-value pair or connector expert, insert the word "no" between the dollar sign and attribute or connector name in instruction step number one (e.g., $noProtocol). There should be no white space between the word "no" and the dollar-sign and attribute or connector name. It is recommended that the word "no" contain only lowercase letters, although this is not necessary. For prototypes that get included unconditionally, substitute the "__always__" marker in place of the attribute name in instruction step number one.
         Indentation in the file should start at 0 spaces. Each line in the file "<expert>_utility_support_defs" must be terminated by a newline.
         In our Socket example, assume that the socket expert requires utility functions to find the Protocol, Port, and Address attribute-value pair specifications in the connector's attribute-value pair list. We name these functions "uni_find_protocol," "uni_find_port," and "uni_find_address," respectively. The contents of our
"socket_expert_support_defs" file look like the following for the prototype declarations:
         $Protocol prototypes\n
char *uni_find_protocol (
#ifdef __STDC__
uni_list av_pairs
#endif
);

$Port prototypes\n
int uni_find_port (
#ifdef __STDC__
uni_list av_pairs
#endif
);

$Address prototypes\n
char *uni_find_address (
#ifdef __STDC__
uni_list av_pairs
#endif
);
[EOF]
         Function Definitions
         To add the utility support function definitions to "uni_utility.c", create the following file in the connector expert's database subdirectory (it is optional):
         "<expert>_utility_support_defs"
         This file may have already been created to add the corresponding function prototypes (see section Function Prototypes above). If so, then simply add to the existing file. The format for the function definitions in this file is identical to the format of the prototypes.
         For functions that get included in the presence of a particular attribute-value pair or connector expert, the
"<expert>_utility_support_defs" file must contain the following:
         1. the attribute name of the attribute-value pair specification (as it appears in the file "<expert>_connector_attribute_enums") or the name of the connector expert (as it appears in the file "<expert>_connector_enum"), preceded by a dollar sign character, `$', followed by white space and then the word "definitions" (preferably in lowercase letters, but this is not required), followed by a newline (e.g., $Protocol definitions\n)
         2. a static or global function definition that follows the line with the `$<attribute name>' on it, using C language syntax declared to be both ANSI C and K&R C compatible, as follows:

int function_name (

#ifdef __STDC__
(arguments)
#else
(argument names)
argument declarations
#endif
{
<specific code goes here>
}
         For functions that get included in the absence of a particular attribute-value pair or connector expert, insert the word "no" between the dollar sign and attribute or connector name in instruction step number one (e.g., $noProtocol). There should be no white space between the word "no" and the dollar-sign and attribute or connector name. It is recommended that the word "no" contain only lowercase letters, although this is not necessary. For prototypes that get included unconditionally, substitute the "__always__" marker in place of the attribute name in instruction step number one.
         Indentation in the file should start at 0 spaces. Each line in the file "<expert>_expert_support_defs" must be terminated by a newline.
         In our Socket example, assume we now wish to define the functions "uni_find_protocol," "uni_find_port," and "uni_find_address," whose prototypes we've added in the previous section:
         $Protocol definitions\n
char *uni_find_protocol

#ifdef __STDC__
(uni_list av_pairs)
#else
(av_pairs)
uni_list av_pairs;
#endif

{
<specific code goes here>
}

$Port definitions\n
char *uni_find_port

#ifdef __STDC__
(uni_list av_pairs)
#else
(av_pairs)
uni_list av_pairs;
#endif

{
<specific code goes here>
}

$Address definitions\n
char *uni_find_address

#ifdef __STDC__
(uni_list av_pairs)
#else
(av_pairs)
uni_list av_pairs;
#endif

{
<specific code goes here>
}
[EOF]