Chapter 8. Creating User-defined Data Types
Prev |  | Next

## Chapter 8. Creating User-defined Data Types

This chapter describes how to create your own data types for handling data that does not fit one of the existing data types. The concept of the “user-defined” data type is discussed and the syntax for the Explorer data typing language (ETL) is laid out.

The chapter provides examples of the typing language, as well as the specification for a new data type and code for modules that can use it.

## Overview

All the data that Explorer manipulates must fit into a recognized Explorer data type. Explorer has several built-in data types that satisfy the needs of nearly all users and fit most scientific data; however, sometimes the existing Explorer data types do not adequately describe the data you want to visualize. Enter the user-defined data type (UDT), designed specifically for the task at hand. For example, one might define a new data type for quantum dynamic calculations on molecules, along with a suite of modules to do the calculations and create geometric representations suitable for use by the other modules in Explorer.

Explorer provides a data typing language (ETL) for defining data types that can be passed among modules. Such data types are called root types. Using ETL, you can create a new root data type for use in custom-built Explorer modules, in the Map Editor, and in the Module Builder. The Explorer data types cxLattice, cxParameter, cxPyramid, cxGeometry, and cxPick were created using ETL and are examples of how to use ETL.

For a new data type to work in Explorer, it must have a C structure definition that can be used to build modules, as well as a description of the type that Explorer and the Module Builder can load at run-time. It is also useful to have a library of accessor functions that allow C, C++, and Fortran users access to the type with a minimum of programming. Explorer allows you to create all these things.

When you create a new data type, you have to define all aspects of the structure that Explorer may need to know at any time. You also need to write new modules to accept, process, and output the data conveyed in the new data type, so the design of your data type should be carefully thought out.

Most users will find that the standard Explorer data types are sufficient for their needs. Furthermore, a large number of modules are already built to handle the standard data types. For those with data from specialized domains, the effort to define a new data type is relatively small; however, the true cost of using a new type effectively is considerably greater because of the larger investment in module writing.The creation of new types, therefore, is best suited to those users already intending to write a suite of new modules.

## Creating a Data Type

Explorer data types consist of a root data type and, optionally, one or more subsidiary data types. The root data type is the named structure that appears as a data type on module input and output ports and can be passed between modules.

The subsidiary data types expand the functionality of the root data type but cannot stand alone on a port. For example, cxConnection and cxPyramidDictionary are subsidiary data types used in defining the root data type, cxPyramid. All these type definitions are collected together in a type declaration file.

### Type Declaration File

Data type descriptions reside in type declaration files, which end with the suffix .t (for “type”). You create and name the type declaration file when you define the new data type.

This is the basic structure of a modulename.t file:

1. Any necessary include statements for data types defined elsewhere:

       #include <---->
       #include <---->
       ...

2. The root type definition:

       root typedef struct {
       ..
       } RootTypeName

3. Subsidiary type definitions:

       shared typedef struct {
       ...
       } Name1
       closed typedef struct {
       ...
       } Name2
       typedef struct {
       ...
       } Name3

The keywords used in each type definition are defined in “Syntax.”

As can be seen from this outline of the modulename.t file contents, the root type is a `struct`, similar to a C structure. The building blocks of the type include other `struct`s, enumerations, unions, and arrays of any of these.

### Naming Files and Data Types

All data types have names. The naming conventions are similar to those in C, that is, the first character must be a letter, but the rest of the name may contain digits and the underscore (_). Data type names may be of any length and case is significant. The type filename and data type name must agree. For example, if the data type is to be called myCylinder, the file must be named myCylinder.t. If the file is named bxMyCylinder.t, the data type must be called bxMyCylinder.

A single type declaration file may contain several type declarations if the root data type contains subsidiary data types. The order of data type declarations in the file is not important; they are sorted during the translation into C.

## Using the ETL

The Explorer data typing language resembles the structure definition syntax of C in many respects. It uses the constructions `typedef` and `struct`, and a construction similar to union, called `switch`.This section takes an in-depth look at the typing language, including the definition of its syntax and some examples.

The presentation of the syntax follows the format used in The C Programming Language, Second Edition, by B.W. Kernighan and D.M. Ritchie (Prentice-Hall, 1988). The syntax and some discussion of the restrictions placed on forming compound types follow.

### Conventions

These conventions apply in this chapter:

- Syntactic object categories are indicated by italics, for example, integer_value.

- Literal words and characters such as Explorer keywords are indicated by `Courier` font, for example, `closed`.

- Alternate categories are listed sequentially on separate lines.

- Sets of narrow alternates are listed on one line, preceded by the phrase one of.

- An optional symbol has the suffix opt.

- The double period `..`denotes one of the range of values in the ASCII collating sequence; for example, `a..z` indicates one of the range of characters `a,b,c,...,z`.

- The triple period ... denotes a repeated syntactic object, for example, case ... .

### Syntax

There is a small set of special keywords used in defining an Explorer data type.

`root` |  | Indicates a data type accessible by module input and output ports, and therefore transportable from module to module. For example, the Explorer data types cxLattice, cxGeometry, cxPick, cxPyramid and cxParameter are all root data types.`root` modifies a `typedef struct` definition. Root structures are reference-counted.
`shared` |  | Modifies an instance of a subsidiary structure used in creating another, containing structure. It indicates that the structure should have a reference count associated with it. For example, the cxData, and cxCoord structures, contained within cxLattice, are reference-counted.
`closed` |  | Used to hide the contents of a structure when the type is displayed in the Module Builder connections window. It can be used either as a modifier to a `typedef` or a member of a `struct`. It is used in recursive data types, for example the recursive loop in cxPyramid. It can also be used for moving the data in a structure intact through a module, for example, cxGeometry.
`typedef` |  | Defines a new type name.
`struct` |  | Indicates a structure definition. A structure is a collection of named members of different kinds.
`switch` |  | Indicates a discriminated union. A discriminated union is a union structure for which another variable (the discriminator) determines which variant of the union is active.
`case` |  | Indicates one variant of a discriminated union.
`enum` |  | Creates a new set of enumerated constants.

There are special language symbols for a few of these keywords.

Other keywords are used in-place in subsequent sections of the language definition.

|  | `Keyword`
port_modifier: |  | `root`
reference_modifier: |  | o`ne of root shared`
modifier: |  | port_modifier, reference_modifier
closed: |  | `closed`

### Rules for Type Definitions

Declarations in the Explorer typing language (ETL) are more constrained than in C. These are the basic rules for data type definitions:

- Comments may be placed anywhere in a type definition file, enclosed by `/*` and `*/` (see “Inserting Comments”).

- DISCRIMINATOR and CONSTANT in a switch_type statement must be of the same enumerated type (see “Composition Rules”).

- BOUND must be a positive integer scalar or an array of positive integers (see “Array Dimensioning”).

- Explorer module ports may have any type that is a reference_type (see “Defining a New Type”).

- Shared memory types may be defined in any order.

### Naming Conventions

There is a a standard naming convention for data types defined by Explorer. A standard scheme makes it easy to recognize the different forms of names used in a type and to identify their purpose.

Here are the standard Explorer conventions:

Type names |  | In a concatenation of words, each word after the first is capitalized. No underscores are used, and the leading word or prefix is not capitalized. For example, here are type names for a data and a pyramid dictionary type:
    typedef struct {  } cxData;
    typedef struct {  } cxPyramidDictionary;
Member names |  | A concatenation of words capitalizes each word after the first. No underscores are used, and the leading word or prefix is not capitalized. For example, here are type names for an nDim, a dims, and a primType variable:
    long         nDim;
    long         dims;
    cxPrimType   primType;
Enumeration values |  | Words are all lowercase and separated by underscores. The enumerated values typically contain the enumeration type name as a prefix. For example, here are the enumerated values for enumeration types cxCompressType and cxPrimType:
    typedef enum {
            cx_compress_none,
            cx_compress_unique,
            cx_compress_multiple
    } cxCompressType;

    typedef enum {
            cx_prim_byte,
            cx_prim_short,
            cx_prim_long,
            cx_prim_float,
            cx_prim_double,
            cx_prim_string
    } cxPrimType;

### Scalar Types

These are the basic scalar types that Explorer will accept.

Table 8-1. Scalar Types in Explorer

Scalar Type | C | Fortran
--- | --- | ---
char | char type |
short | short type |
int | long int type | integer type
long | long int type | integer type
float | float type | real type
double | double type | double precision type
string | null-terminated string | character type (converted to null-termination)

The grammar fragments are:

|  | `Scalar Type`
base_integer |  | `char, short, int,` or`long`
floating |  | `one of float double`
string |  | `string`

#### Signing Integer Types

There are also sign modifiers for the base_integer types, namely:

- signed

- unsigned

#### Signing Other Scalar Types

According to the C language specification, scalars of type `short`, `int`, and `long` whose type is not specified are considered signed. Also, in Explorer `char` scalars whose type is not specified are considered unsigned, independent of the local compiler interpretation of `char`. For example, on Silicon Graphics IRIS computers, the C compiler treats `char` as unsigned, while the C++ compiler treats `char` as signed.

Explorer typing forces `char` to unsigned `char` so that both languages treat an Explorer `char` variable identically.

sign |  | `one of signed unsigned`
integer |  | signopt base_integer
simple_type |  | integer, floating, string

### Enumerated Constant Types

You can define, as a new type, an enumeration of consecutive named constants starting at zero.

enum_type |  | `enum` `{` name `}` `enum` `{` name, name ... `}`

### Composition Rules

An Explorer type includes structures composed of the basic scalar types in three forms:

- array

- switch (union)

- structure

The composition rules may be applied both to scalars and to other composed types. Thus, you may construct an array of integers, and also an array of enumerated constants, structures, unions, or arrays,. The language syntax for these elements is shown below.

#### Struct Structures

`struct` is an Explorer structure, almost identical in syntax to the C structure into which it is transformed. It looks like this:

struct_type |  | `struct {` member ...`}` (member is defined below)

You must define switch discriminator and array subscript variables within the same `struct` definition as the switch and array variables to which they refer.

#### Switch Structures

`switch` is an Explorer union, declared as a named structure, and indicating a discriminator member that can indicate which variant of the union to consider at run-time.

The structure looks like this:

switch_type |  | `switch` `(` DISCRIMINATOR `)` `{` case ... `}`
case |  | `case` CONSTANT : member ...

DISCRIMINATOR is a name of type enum_type; CONSTANT is a name that is a possible value of type enum_type. For example, cx_prim_type.

### Defining a New Type

In C, it is possible to declare a structure without defining that structure as a new type. In Explorer, you must name all new types in order to declare members of that type. The `typedef` statement defines a new type from a simple type, a `struct`, or an enumeration. It can also be used to create a new type from a previously created type, in effect creating a pseudonym for an existing type. These operations are defined below:

abstract_type |  | simple_type struct_type enum_type
named_type |  | STANDARD_TYPE
reference_type |  | REFERENCE_TYPE
defined_type |  | simple_type switch_type named_type reference_type
standard_definition |  | closedopt`typedef` abstract-type type_name;
reference_count_definition |  | closedopt modifier `typedef` struct_type type_name;

STANDARD_TYPE is a name of type standard_definition; REFERENCE_TYPE is a name of type reference_count_definition.

### Including Other Type and Header Files

Both C header files and other Explorer type files (with a .t suffix) may be included into a type file using the standard C preprocessor syntax, which uses a `#` character in the first column. If a file TypeA.t needs definitions from file TypeB.t, it must include TypeB.t in a `#include` statement.

Inclusion of an Explorer type file has the effect of incorporating the included file into the current typing file. This allows you to reuse previously defined types; for example, the cxPyramid type uses the cxLattice type. Inclusion of a modulename.t file also means the corresponding modulename.h file will be included in the resulting C header file.

Inclusion of a C header file has no effect on the actual type processing, but only serves to put the same include statement into the resulting C header file.

FILENAME is a name referring to a file in the file system, located in $EXPLORERHOME/include/cx or in the current directory.

include |  | `#include` `<FILENAME> #include` `"FILENAME"`

### Structures

There are two forms of structures in the Explorer typing language, in-line and reference-counted structures. Reference-counted structures are denoted by the `root` and `shared` keywords in the typing language. Both keywords imply reference-counting, but `root` has other meanings as well, described below.

#### In-line Structures

In-line structure members reside contiguously with the preceding and following members in the enclosing structure.

Thus, the following example creates a `twoInt` structure named two with two members, two.a and two.i.b:

         typedef struct {
               int b;
         }oneInt;

        typedef struct {
              int a;
              oneInt i;
         } twoInt;

        twoInt two;

The actual storage layout of two is contiguous, with member a followed by`i.b`. Member names in this example are `two.a` and `two.i.b`.

The grammar fragment is:

standard_definition |  | closedopt `typedef` abstract-type type_name;

#### Reference-counted Structures

Reference-counted structure members do not reside contiguously with the preceding and following members in the enclosing structure. Rather, the entire structure occupies its own section of memory and is referenced by a pointer. The following example creates a `twoFloat` structure named two with two members, two.a and two.i->b:

       shared typedef struct {
              float b;
       } oneFloat;

       typedef struct {
               float a;
               oneFloat i;
       } twoFloat;

       twoFloat two;

The actual storage layout of two is the value of a followed by the address of an instance of a oneFloat. Member names in this example are `two.a` and `two.i->b`.

The grammar fragment is:

reference_count_definition |  | closedopt modifier `typedef` struct_type type_name;

For more on reference counting, see “Understanding Reference Counting.”

#### Differences Between Structures

There are two differences to be noted in accessing the in-line and the reference-counted structures.

The first difference is that a `shared` member will always be a pointer to a structure, rather than an in-line structure. An array of a `shared` type is really an array of pointers to shared structures.

The following example shows a structure member two with two members, two.n and two.i, where the several double precision numbers in two are two.i[j]->b, for values of j between `0` and n-1:

        shared typedef struct {
               double b;
        } oneDouble;

        typedef struct {
                int   n;
        oneDouble i[n];
      }  doubleArray;

       doubleArray two;

The second main difference, and the reason why there is a distinction between in-line and reference-counted of structures, is that the reference-counted structure has some additional information that allows the structure to be shared between two or more data sets. Because the reference-counted structure is referred to by a pointer, several data sets can hold the same pointer: a single update of the shared data affects all data sets. It is impossible for two or more structures to share a single in-line structure.

| Note: The user should not change values within the reference-counting structure, nor rely on the form or content of that structure, as it is subject to change in subsequent releases of Explorer.

#### Examples of Reference-counted Structures

The cxData and cxCoord types are implemented within a cxLattice as reference-counted structures. This means that several lattices could share data or coordinates, for example to allow the same data values to be mapped simultaneously onto several coordinate mappings without duplicating the (potentially large) set of data.

In addition to the reference-counted meaning of the `root` keyword, `root` also implies that the type is visible as the type of an Explorer module's port. For example, cxLattice is a root type and is a legal port type, but cxData (contained in cxLattice) is a `shared` type, but not a `root` type, and is thus not a legal port type. Hence `root` implies `shared`, but `shared` does not imply `root`.

### Structure Member Declaration

At most, one variable may be declared in each member statement. This is distinct from the C style, where multiple variables of the same type may be declared together. The grammar fragment is:

simple_type VAR;

For example, this is legal in Explorer:

       int a;       /* Legal */
       int b;

but this is not:

       int a, b;    /* Illegal */

The most general form of a member declaration is:

member |  | closedopt defined_type VAR array_specifieropt labelopt; closedopt switch_type VAR array_specifieropt labelopt;

However, the example

simple_type VAR;

is actually much simpler than the general form. It has no `closed` keyword, uses an integer simple_type, is not an array, and has no label.

Other simple-typed members include:

    int a;
    unsigned long b[n] “Value of B”;
    closed   int c;
    float    d[ a, a, 3 ];
    double   e    "Real Number E";

Table 8-2 lists the parts of a simple-typed member.

Table 8-2. A Simple-typed Member

simple_type | Name | Bound | Label
--- | --- | --- | ---
unsigned long | b | [n] | “Value of B”

You could also use common Explorer types in this example, where you use other defined_type types:

    cxLattice a;
    cxLattice b[10];
    closed cxLattice c "Not visible in Module Builder";

### Array Dimensioning

Explorer arrays can be dimensioned by a combination of integer constants, scalar integer variables, and arrays of integers. It is important to understand how the array declaration in the Explorer typing language translates into an array of bytes in memory and how to access that memory in C, C++, or Fortran.

All Explorer arrays are represented by 1-D arrays of memory. The indexing into this long array depends on the order of the array bounds in the array declaration. Array bounds are given in the normal Fortran order, so that the first bound represents the fastest varying index. For example, the following 3-D array ThreeD has shape m by n by p, with the m-based index varying fastest:

    int m;
    int n;
    int p;
    int ThreeD[ m, n, p ];

The equivalent Fortran declaration is:

    integer ThreeD(m, n, p)

The equivalent C or C++ declaration is:

`long ThreeD[p, n, m];` |  | Incorrect: this is illegal in C, but gives the sense.
`long ThreeD[p][n][m];` |  | Incorrect:the generated array has only one dimension, not three as suggested here.
`long ThreeD[p*n*m];` |  | Correct: this is the size. You need to know m varies fastest.

Arrays can have integer constants or scalars as bounds. The constant or the value of the variable is used at run-time to determine the array length. Arrays can also have other arrays as dimensioning variables, as in the data array of cxLattice;

    long     nDim;
    long     dims[nDim];
    double   values[nDataVar, dims];

In this case, the array contents are treated as a list of scalar values, so that the previous example is equivalent to the following one for `nDim = 3`:

    long      dims[3];
    double    values[ nDataVar, dims[0], dims[1], dims[2] ];

Thus the length of an array can be calculated as the product of all of its dimensioning bounds, where a dimensioning array (for example, dims) has a product equal to the product of its integer contents.

array_bound |  | positive_integer_value BOUND

BOUND is a name of type integer or an array of type integer

array_specifier |  | `[` array_bound `]` `[` array_bound, array_bound ... `]`
member |  | closedopt defined_type VAR array_specifieropt labelopt; closedopt switch_type VAR array_specifieropt labelopt;

### Assigning Labels

Most members in a type should be assigned labels. Labels are used in the creation of automatically generated API routines for programmatic access to the data types. All members that are to be accessed through this API must have labels. Labels are also used in the Module Builder Connections window for data type member wiring.

Members that can get labels include scalars, arrays, and reference-counted structures. Members that do not get labels include in-line structures (see below), switch structures, and type definitions.

The author of the type file may assign labels to structure members. The label is a quoted text string following the variable name and optional array bounds. Table 8-3 how the scalars are labeled in a lattice.

Table 8-3. Labels on Lattice Scalars

Scalar Type | Name[Bounds] | Label
--- | --- | ---
long | nDim | “Num Dimensions”;
long | dims[nDim] | “Dimensions Array”;

member |  | closedopt defined_type VAR array_specifieropt labelopt; closedopt switch_type VAR array_specifieropt labelopt;

#### Constructing Labels

Most members of an Explorer structure are given a text label for use elsewhere in Explorer, for example, in creating automatically generated API routines. If the user does not supply a label on a member which requires a label, the Explorer type compiler assigns a default label computed from the member name.

Explorer makes some effort to ensure that no two assigned labels are identical, but does not try to avoid collisions between automatically generated labels and user-supplied ones.

blank |  | self defining
tab |  | self defining
underscore |  | - (Note: Do not overlook it.)
alpha |  | one of `A..Z a..z`
numeric |  | `0..9`
non_zero |  | `1..9`
character |  | text whitespace
text |  | alpha numeric underscore
whitespace |  | one of blank tab
label |  | `"`character ... `"`
integer_value |  | numeric numeric ...
positive_integer_value |  | non_zero non_zero numeric non_zero numeric ...
name |  | alpha alpha text alpha text ...
member |  | closedopt defined_type VAR array_specifieropt labelopt; closedopt switch_type VAR array_specifieropt labelopt;

VAR is an item of name.

### Inserting Comments

Explorer type files use the standard C comment syntax, which is a block of text surrounded by the delimiters `/*` and `*/`. Comments may be inserted anywhere in the file.

comment |  | `/*` character ... `*/`

## Differences between ETL and C

The Explorer typing system lacks certain features that C structure definitions offer. There are three obvious differences, discussed below.

### No Separating Comma

The comma “,” does not work in ETL as it does in C. You must define each variable on a separate line. For example, this structure in C:

    typedef struct {
           float x,y; /* Invalid in ETL */
    } mine;

looks like this in ETL:

    typedef struct {
           float x; /* Correct in ETL */
           float y;
    } mine;

### No Pointer Variables

The ETL does not allow you to explicitly declare pointer variables in Explorer types. Thus you may not declare an object by reference. This is because Explorer transcription routines need to know the size of the object pointed to, and a pointer to an object could be a pointer to one or many consecutive objects. However, in the generated C structure, two Explorer type members are represented as pointers to allow for multiple references. These are the array and the reference-counted structures (`shared` and `root` structures). The array has a variable dimension determined at run-time, so it cannot be stored in-line in the containing structure. The reference-counted structures are described above.

### No Anonymous Structure Arrays

ETL must be able to calculate the size of every non-in-line structure it manipulates so that it can handle the transcription of data properly. To do this, it needs a name for the structure.

This means you must use a `typedef` statement to name all such structures, and the name must be unique. If the structure is anonymous (or if the name is duplicated), there is no name handle for ETL to use when calculating its size.

This example illustrates the premise that all types must be explicitly and uniquely named. You may not declare an Explorer type of the following form, for Explorer would have no way of naming the structure or of representing its size and contents internally:

    struct {
        int a;
        int b;
    } s;        /* WRONG: Can't have an anonymous structure. */

The correct way to do this is to define a new type:

    typedef struct {
       int a;
       int b;
    } twoInt;   /* RIGHT: Structure has a type. */

    twoInt s;

This example illustrates the premise that an embedded `struct` must be a named type unless it is in-line

    typedef struct {
       int n;
       struct {

float x; (Wrong: Undefined (no name), and therefore anonymous structure. No name available for computing the size of “foo.”)

          float y;
       } foo[n];
    } mine;

    typedef struct {
       float x;
       float y;   (The foottype is first defined in its own typedef)
    } footype;

    typedef struct {
        int n;  (Size of “foo” computable from n* sizeof(footype).)
        footype foo[n];
    } mine;

## Scoping

There are several issues relating to scoping in Explorer's typing system. Scoping refers to the structure context in which a variable is known to exist (by Explorer's typing system). The scoping requirements limit the extent to which Explorer must search for a particular structure member.

### Ordering Array Members

Array bounds must exist at the same level of lexical scope where they are used. Furthermore, the array bound member must precede the array in which it is used in the structure. The following examples illustrate this concept.

In this example, the variables m and n are used without having been defined.

    typedef struct {
         int    len;
         float  vec[len];/* Correct scope for len  */
         float  box[m,n];/* Incorrect scope for m and n. */
    } Example1;

Here, the first use of len occurs before it has been defined.

    typedef struct {
          float   vec[len];     /* Len not yet set -- incorrect.*/
          int     len
          float    box[len,len];/* Len set -- correct. */
    } Example2;

The member order in this fragment of cxLattice is correct:

    root typedef struct {
         long   nDim            “Num Dimensions”;
         long   dims[nDim]      “Dimensions Array”;
         ...
         cxLattice;

But this order is wrong, because nDim is used before it has been defined:

    root typedef struct {
        long   dims[nDim]   “Dimensions Array”
        long  nDim          “Num Dimensions”;
        ...
    } cxLattice;

| Note: Shared memory types, such as cxData, cxCoord, and cxLattice, may be defined in any order.

#### Using Switch Discriminators

In general, switch discriminators must exist in scope where they are used. However, it is acceptable for an array bound or switch discriminator to exist in an outer scope, for example in the containing structure, provided that the inner structure is an in-line structure and not a reference-counted one.

For example, in the cxData structure, primType exists outside the switch scope and is used correctly inside it:

    shared typedef struct {
       long            nDim;
       long            dims[nDim];
       long            nDataVar       "Num Data Variables";
       cxPrimType      primType       "Primitive Data Type";
       switch          (primType) {
          case cx_prim_byte:
               char     values[nDataVar, dims]  "Data Array";
          case cx_prim_short:
                short    values[nDataVar, dims] "Data Array";
          case cx_prim_long:
               long     values[nDataVar, dims]  "Data Array";
          case cx_prim_float:
               float    values[nDataVar, dims]  "Data Array";
          case cx_prim_double:
               double   values[nDataVar, dims]  "Data Array";
         } d;
    } cxData;

### The Resulting C Structure

To be useful to the Explorer programmer, the Explorer typing system provides a data structure that can be accessed through a conventional programming language. Explorer types are translated into C structures, which can be accessed directly from C or through a functional application programmer interface (API) from C, C++, or Fortran.

Here are some points to note about the structure:

- There is the direct, natural conversion from most of Explorer's typing language to C structures.

- The scalars are converted as described in “Scalar Types.”

- Explorer arrays are handled as one-dimensional C arrays, but may be considered as multi-dimensional arrays by using a Fortran indexing scheme.

- Enumerated constants are translated to C enumerated constants with an origin at zero.

- The Explorer `struct` composition is identical to the C `struct` composition, however, `shared` and `root` structures add a reference-counting substructure as a prefix to the user-defined structure.

Labels for the structure members are not reflected in the resulting C structure, but are used to create the API for accessing the data structure and the menus in the Module Builder.

The `switch` statement is translated into a `union` of variant structures in the resulting C code. Each variant structure is named by the enumerated constant value that selects the variant. For example, this Explorer structure translates into the subsequent C structure:

Explorer `switch` structure:

       switch (myType) {
       case cx_my_long:
           long    a;
       case cx_my_float:
            float         b;
        case cx_my_double:
            double  d ;
       } s;

C equivalent (`union` structure):

     union {
            struct {
                  long  a;
                        } cx_my_long;
            struct {
                  float        b;
                     } cx_my_float;
            struct {
                   double d;
                     } cx_my_double;
       } s;

An important detail to notice here is that the structure members are accessed in C as members:

      s.cx_my_long.a
      s.cx_my_float.b
      s.cx_my_double.d

The `closed` keyword has no effect on the generated C structure.

## Summary of ETL Syntax

This section summarizes the ETL syntax discussed in the previous sections.

- Syntactic object categories are indicated by italics, for example, integer_value.

- Literal words and characters such as Explorer keywords are indicated by `courier` font, for example, `closed`.

- Alternate categories are listed sequentially on separate lines.

- Sets of narrow alternates are listed on one line, preceded by the phrase one of.

- An optional symbol has the suffix opt.

- The double period `..`denotes one of the range of values in the ASCII collating sequence; for example, `a..z` indicates one of the range of characters `a,b,c,...,z`.

- The triple period ... denotes a repeated syntactic object, for example, case ... .

- blank - self defining

- tab - self defining

- underscore - _ (Note: Do not overlook it.)

- alpha - one of`A..Z a..z`

- numeric - `0..9`

- non_zero - `1..9`

- text - alpha, numeric, underscore

- whitespace - one of blank tab

- character - text, whitespace

- label - character ... `"`

- comment - `/*` character ... `*/`

- include - `#include` `<`FILENAME`>, #include` `"`FILENAME`"`

- integer_value - numeric, numeric ...

- positive_integer_value - non_zero, non_zero numeric, non_zero numeric ...

- name - alpha, alpha text, alpha text ...

- base_integer - one of `char short long`

- sign - one of `signed unsigned`

- integer - signopt base_integer

- floating - one of `float double`

- stri - `string`

- simple_type - integer, floating, string

- struct_type - `struct {` member ...`}` (member is defined below)

- enum_type - `enum` `{` name `},` `enum` `{` name, name ... `}`

- abstract_type - simple_type, struct_type, enum_type

- case - `case` CONSTANT : member ... (member is defined below)

- switch_typ e - `switch` `(` DISCRIMINATOR `)` `{` case ... `}`

- named_type - STANDARD_TYPE

- reference_type - REFERENCE_TYPE

- defined_type - simple_type, switch_type, named_type, reference_type

- array_bound - positive_integer_value , BOUND

- array_specif ic -`[` array_bound `], [` array_bound, array_bound ... `]`

- closed - `closed`

- member - c losedopt defined_type VAR array_specifieropt
  lelopt;
  closedopt switch_type VAR array_specifieropt labelopt;

- port_modifier - `root`

- reference_modifier - one of `root shared`

- modifier - port_modifier, reference_modifier

- standard_definitio - closedopt`typedef` abstract-type type_name;

- reference_count_definition - closedopt modifier `typedef` struct_type type_name;

VAR is a name; BOUND is a name of type integer; DISCRIMINATOR is a name of type enum_type; CONSTANT is a value of type enum_type; STANDARD_TYPE is a name of type standard_definition; `REFERENCE_TYPE` is a name of type reference_count_definition

## Building a Type Declaration

You now need to build the type declaration and install the files that Explorer creates in a directory of your choosing. Once this is done, you can run the Module Builder. When you open the Connections window, the new data type will show up on the port menus.

### Building the Type and Related Files

Let us suppose that you have created the data structure for the myCylinder data type by using the grammar and principles described in “Using the ETL.” You have saved it in a file called myCylinder.t.

To build the data type and install it in weExplorer, follow these steps.

1. Make a subdirectory in your current directory for constructing your data type and its related files. Use the directory name ~/explorer/cyl.

   Go to that directory:

       mkdir ~/explorer/cyl
       cd ~/explorer/cyl

2. Put the myCylinder.t file in the subdirectory.

3. Make a file called TYPES in the same subdirectory and put the name of the data type in it. For example,

       cat > TYPES
       myCylinder
       ^D

   This TYPES file now contains the name `myCylinder`.

4. Create a Makefile for your ~/explorer/cyl directory by running the type Makefile generator. Type the command:

       cxmkmf

   Explorer then creates an Imakefile and a Makefile.

5. Build the type and related files by typing:

       make

   or

       make all

6. Install the type into the $EXPLORERUSERHOME subdirectory so that it will be visible to all of Explorer by running the command:

       make install

This command installs the made files in $EXPLORERUSERHOME. These files are:

    include/cx/myCylinder.t
    include/cx/myCylinder.h
    include/cx/myCylinder.inc
    include/cx/myCylinder.api.h
    include/cx/myCylinder.api.inc
    lib/libmyCylinder.a
    types/myCylinder.type
    man/man3/myCylinder.3

### What the Files Do

When you run `make install`, Explorer translates the language in the type declaration file into C and creates the myCylinder.type file. It also creates the header file for the data type, myCylinder.h and the include file myCylinder.inc. You include this file in the user function file (for example, in ReadCyl.c) when you build a module that makes use of the Cylinder data type (for example, to read cylinder data).

The files have these uses:

include/cx/myCylinder.t |  | The original type description file. Placed into a known location so that it can be included in other type files. For example, cxLattice.t is used in cxPyramid.t.
include/cx/myCylinder.h |  | The generated C structure definition for the type. This header file is included by C programs that manipulate the type.
include/cx/myCylinder.inc |  | The generated Fortran definition of all enumerated constants used in building the type. This header file is included by Fortran programs that manipulate the type. Since the Fortran programmer must access the type through a set of subroutines and functions, the enumerated constants are necessary as function parameters. No structure information is included in the Fortran `include` file, since Explorer structures are not accessed directly from Fortran.
include/cx/myCylinder.api.h |  | The C header file containing function prototypes for the application programmer interface to the type's accessor functions. See the library file lib/libmyCylinder.a below for a discussion of the accessor functions.
lib/libmyCylinder.a |  | In the process of building a type, several accessor functions are created to allocate the type, to set and get members of the type, to inquire about the enumerated type and length of members, and to read and write data of this type in the standard Explorer transcribed form. Both C and Fortran versions of the library are created and stored in the archive file lib/libmyCylinder.a. In addition, the library archive contains an object file myCylinder.meta.o, which contains a C structure describing the type. This “meta-type” description is required by the type accessor library and fully describes the type: it is a compiled object representation of the myCylinder.t file.
types/myCylinder.type |  | The myCylinder.type file is a binary version of the myCylinder.meta.o structure that can be loaded by Explorer at run-time. It is used by the Map Editor and Module Builder to discover the description of types.

Figure 8-1 shows the sequence of events in the build process.

Figure 8-1. Explorer Types Information Flow

## Visibility of Data Types in Explorer

Data types are present in many guises within Explorer. You can use the Module Builder to create a series of modules that use the new data type, then wire the modules into a map in the Map Editor and look at the results. You will be able to see the various manifestations of the data type declarations.

### In the GUI

The data types are visible to users at several places in the GUI (graphical user interface). In the Map Editor, the user sees the data type on each module port when a map is wired. The Map Editor uses the data types and auxiliary constraint information to determine which ports are valid for a given wiring, hence the IO pad highlighting to illuminate potential wiring destinations.

In the Module Builder, the module writer sees a menu of all available port data types when creating input and output ports. Input and output ports can be of any root data type. The Module Builder must configure these menus at run time from the wiring connections made in the Connections panel. Description strings appear as menu items in the Connections window.

### Between Modules

Module communication across socket links, for example between modules on different hosts or on a single host with non-shared memory, requires that both the sender and receiver know how to transmit the data structure. Explorer data structures may have variably sized portions, pointers to shared pieces, and may even be recursive. Each module is compiled with the necessary information about its constituent data types, so that it can correctly transmit, read, and write those types. Furthermore, some modules, such as For and While, must be able to receive and send data structures that were not known when they were built.

## Application Programming Interface

Each data type is a structure made up of several members. The structure members can be scalars of certain types, or compositions of scalars in the form of arrays, unions, and structures. Associated with each member is a set of accessor functions, the application programming interface (API), that allows the user to set and get its value. The API is automatically generated from the label given to each member, either by the user or internally by the typing system. Table 8-4 gives an example of a member:

Table 8-4. Member of a Data Type

Type | Variable Name | Label
--- | --- | ---
short | count | “No. Of Items”

Explorer suppresses spaces and other non-alphanumeric characters such as parentheses, and runs the alphanumeric characters together to form the base name of the API routine. It then adds the type prefix (which for Explorer is cx). For the above example, the API routines would have the base name shortNoOfItems*. A verb (either Get or Set) that indicates the function of the API routine is then appended; for example, to manipulate count, you can call shortNoOfItemsGet and shortNoOfItemsSet.

All members get the functions *Get and *Set, and some members have additional routines. These are listed in the next section, and you can refer to the generated manual page for the exact interface.

### API Member Types and Functions

Here is a list of the types of members and their functions. The type is `myType`, the label is `Label`, and a trailing name indicates the form of access.

#### Functions for All Members

All members of a data type get these routines:

- myTypeLabelGet()

- `elSet()

#### Functions for Reference-counted Structure

The reference-counted structures get these routines:

myTypeAlloc() |  | Allocates the structure
myTypeRead() |  | Reads an Explorer transcribed (ASCII or binary) type file
myTypeWrite() |  | Writes an Explorer transcribed (ASCII or binary) type file
myTypeDup() |  | Duplicates all contents of the data structure

| Note: cxDataRefDec() is used to delete all reference-counted structures.

#### Functions for Arrays

The arrays get these routines:

myTypeLabelLen() |  | Computes the length of the array in words
myTypeLabelAlloc() |  | Allocates the array

#### Dimensioning arrays

These include arrays such as lattice->data->dims.

myTypeLabelProd() |  | Computes the product of the array's entries, to be used in computing the array length of the array for which this is a dimension (or bound).

#### Functions for Members in a Switch

In some cases, several members within a `switch` construct may be given the same label. The members must lie within a discriminated union (a switch) and no two members can lie in the same case of the union. In this case, the several members take on a single identity, with the active member depending on which case of the union is active.

These labelled unions have additional accessor functions:

myTypeLabelType() |  | The primitive data type of the active member
myTypeLabelLen() |  | The length of the active member in words (1 for scalar)

C and Fortran versions of the routines are generated and loaded into the library archive lib/libmyType.a.

#### Memory Handling

All Explorer user-defined type structures are placed in shared memory on machines with shared memory. When you use the <TYPE><LABEL>Set() routines, they free up the memory at the location they are about to overwrite if the member is an array. If the member being overwritten is a reference-counted item, the reference count is decremented.

### Fortran Wrappers

Fortran wrappers are generated for almost all user-defined type API routines. They follow the usual rules for the Explorer Fortran API:

- Scalars are passed by reference in Fortran, rather than by value as in C.

- All user-defined types are passed as integer handles. Arrays are passed by reference to the first entry in the standard Fortran style. In some cases arrays will be returned by functions and thus require use of the `Pointer` construct in Fortran.

#### Suppressing char** Routines

Incorrect wrappers are generated for routines returning a `char**` return value. For example, the prototype in the myType.api.h file for the routine named myTypeItemGet() looks like this:

    char **myTypeItemGet( myType* src, cxErrorCode *ec );

You can suppress generation of these routines by following these steps.

1. `cd` to the directory in which the user is building`myType.api.o`.

2. Create a file Imakefile.default by typing this command at the shell prompt:

       echo "CXC2FPREPROC = c2fpreproc.sed" > Imakefile.default

3. Type this command:

       cp $(EXPLORERHOME)/lib/c2fpreproc.sed c2fpreproc.sed

4. Edit the file c2fpreproc.sed by adding this line at the end:

       s/.*myTypeItemGet.*$//g

Fortran wrapper generation will be suppressed for that routine. If users really want such a routine, they will have to create it themselves, by hand.

#### Fortran Bugs

At this time the Fortran interface is broken for routines with a cxstring ** argument. Such a routine would be generated for a member that is an array of strings, for example:

    int         nFiles               “Number Files";
    string      filename[ nFiles ]   "File Names";

In this case, the routines myTypeFileNamesGet() and myTypeFileNamesSet() would not function correctly.

### Location of API ManPages

The manual pages for the API, also automatically generated, will be installed in $EXPLORERUSERHOME/man/man3. To set the value of the environment variable EXPLORERUSERHOME, refer to “Defining EXPLORERUSERHOME.”

This directory is probably not searched by default by the `man` command, but you can add this directory to your search path by defining the MANPATH variable as follows:

    setenv MANPATH /usr/catman:/usr/man:$EXPLORERUSERHOME/man

## Crossing Machine Boundaries

This applies when you go from one type of machine to another, for example, from an IRIS to a Cray, or when you go from shared memory to non-shared memory on a single IRIS. The Module Control Wrapper (MCW) translates the information from the data type into a contiguous stream of data and sends it from one machine to the other. For the process to work, the other machine must know how to reconstitute this data stream into a recognizable data type. This means that the other system must know about the new data type.

You must recreate the type declaration file for the new data type on the other machine because the modulename.type file is specific to the machine it is created on. For example, an IRIS modulename.type file will provide incorrect information to the part of Explorer running on a Cray.

## Example of a User-defined Type

Here is an example of a user-defined type which is basically an Explorer lattice with the addition of three new scalars (Minimum, Maximum, and Empty). For each data variable, there is a description label string.

This is the data type definition. It is the ETL description of the user-defined type “tstLattice” and resides in /usr/explorer/src/UDTtypes/tstLattice.t.

    #include        <cx/DataCtlr.h>
    #include        <cx/Typedefs.t>
    #include        <cx/cxLattice.t>

    shared root typedef struct {
        long                nDim        “Num Dimensions”;
        long                dims[nDim]  “Dimensions Array”;
        tstData(nDim, dims) data        “Data Structure”;
        cxCoord(nDim, dims) coord       “Coord Structure”;
    } tstLattice;

    shared typedef struct {     /* Explorer Lattice's Data array */
        long            nDim;
        long            dims[nDim];
        float           minimum     “Minimum”;
        float           maximum     “Maximum”;
        float           empty       “Empty”;
        long            nDataVar        “Num Data Variables”;
        string          labels[nDataVar]    “Data Labels”;
        cxPrimType      primType        “Primitive Data Type”;
        switch          (primType) {
           case cx_prim_byte:
              char      values[nDataVar, dims] “Data Array”;
           case cx_prim_short:
              short         values[nDataVar, dims] “Data Array”;
           case cx_prim_long:
              long          values[nDataVar, dims] “Data Array”;
           case cx_prim_float:
              float         values[nDataVar, dims] “Data Array”;
           case cx_prim_double:
              double        values[nDataVar, dims] “Data Array”;
        } d;
    } tstData(nDim, dims);

### Test Module 1

This code uses the user-defined data type, tstLattice. It takes in an Explorer lattice and generates a tstLattice. The easiest way to demonstrate this is to connect GenLat to the input. The code resides in /usr/explorer/src/UDTmods/test.c and /usr/explorer/src/UDTmods/test2.f.

#### C Version:

    #include <cx/DataAccess.h>
    #include <cx/DataTypes.h>
    #include <cx/DataOps.h>
    #include <cx/tstLattice.api.h>
    #define CLEAN(A,B,C) if(memclean((void *)A,B,C)) return

    int memclean(void *ptr, cxErrorCode ier, tstLattice *lat)
    {
      if((ptr == NULL) || (ier != cx_err_none))
      {
        cxDataRefDec(lat);
        return 1;
      } else {
        return 0;
      }
    }

    void test( cxLattice *inLat, tstLattice **outLat )
    {
      cxCoord *coord;
      cxData *data;
      tstData *tdata;
      cxErrorCode err;
      void *inDataArray,*outDataArray;
      int num_bytes,i;
      long nDim,*dims,hasData,nDataVar,hasCoord,nCoordVar;
      cxPrimType primType;
      cxCoordType coordType;
      char **labels;

      cxLatDescGet(inLat,&nDim,&dims,&hasData,&nDataVar,&primType,
                   &hasCoord,&nCoordVar,&coordType);
    /* make the complete tstLattice structure and all components */
      *outLat = tstLatticeAlloc(nDim,dims);
      if(*outLat == NULL) return;
      tdata = tstDataAlloc(nDim,dims,nDataVar,primType);
      CLEAN(tdata,0,*outLat);
      tstLatticeDataStructureSet(*outLat,tdata,&err);
      CLEAN(tdata,err,*outLat);
      outDataArray = tstDataDataArrayAlloc(tdata);
      CLEAN(outDataArray,0,*outLat);
      tstDataDataArraySet(tdata,&outDataArray,&err);
      CLEAN(tdata,err,*outLat);
      cxLatPtrGet(inLat,&data,&inDataArray,&coord,NULL);
      tstLatticeCoordStructureSet(*outLat,coord,&err);
      CLEAN(tdata,err,*outLat);

    /* It's all allocated, now fill it */
      tstDataMinimumSet(tdata,0.0,&err);
      CLEAN(tdata,err,*outLat);
      tstDataMaximumSet(tdata,1.0,&err);
      CLEAN(tdata,err,*outLat);
      tstDataEmptySet(tdata,-999.,&err);
      CLEAN(tdata,err,*outLat);
      labels = tstDataDataLabelsGet(tdata,&err);
      CLEAN(tdata,err,*outLat);
    /* I lied. Now allocate the label strings long-hand */
      for(i=0;i<nDataVar;i++)
      {
        labels[i] = cxDataMalloc(sizeof(“label 99”)+1);
        CLEAN(labels[i],err,*outLat);
        sprintf(labels[i],”label %d”,i);
      }
      num_bytes = cxDataPrimSize(data);
      num_bytes *= cxDimsProd(nDim,dims,nDataVar);
      bcopy(inDataArray,outDataArray,num_bytes);
    }

#### Fortran Version:

    #define CLEAN(A,B,C) if(memclean(A,B,C) .gt. 0) return
    c     fortran example for user-defined types
    c
          subroutine test(inLat, outLat)
          implicit none
          integer inLat, outLat, coord,data,err,tdata, memclean
          character*1 inDataArray(1),outDataArray(1)
          integer nDim,dims(1),hasData,nDataVar,hasCoord,nCoordVar
          integer primType, coordType
          character*10 labels
          integer plabels(1),inull,num_bytes,i
          real zero,one,empty
          pointer (pdims,dims)
          pointer (pdims1,dims1)
          pointer (pin, inDataArray)
          pointer (pout, outDataArray)
          pointer (pplabels, plabels)
          pointer (ptmp, labels)
          pointer (null,inull)
          parameter (zero = 0.0 )
          parameter (one = 1.0 )
          parameter (empty = -999.)
            integer tstLatticeAlloc, tstDataAlloc, tstDataDataArrayAlloc
          integer tstDataDataLabelsGet
          integer cxDimsProd, cxDataPrimSize,cxDataMalloc

          call cxlatdescget(inLat,nDim,pdims,hasData,nDataVar,
         +   primType,hasCoord,nCoordVar,coordType)
          outLat = tstLatticeAlloc(nDim,dims)
          if(outLat .eq. 0) return
          tdata = tstDataAlloc(nDim,dims,nDataVar,primType)
          CLEAN(tdata,0,outLat)
          call tstLatticeDataStructureSet(outLat,tdata,err)
          CLEAN(tdata,err,outLat)
          pout = tstDataDataArrayAlloc(tdata)
          CLEAN(pout,0,outLat)
          call tstDataDataArraySet(tdata,pout,err)
          CLEAN(tdata,err,outLat)
          call cxLatPtrGet(inLat, data, pin,coord,null)
          call tstLatticeCoordStructureSet(outLat, coord,err)
          CLEAN(tdata,err,outLat)
          call tstDataMinimumSet(tdata,zero,err)
          CLEAN(tdata,err,outLat)
          call tstDataMaximumSet(tdata,one,err)
          CLEAN(tdata,err,outLat)
          call tstDataEmptySet(tdata,empty,err)
          CLEAN(tdata,err,outLat)
          pplabels = tstDataDataLabelsGet(tdata,err)
          CLEAN(tdata,err,outLat)
          do i=1, nDataVar
             plabels(i) = cxDataMalloc(10)
             CLEAN(plabels(i),err,outLat)
             ptmp = plabels(i)
             write(labels,”('label ',i2,a1)”)i,0
          end do
            num_bytes = cxDataPrimSize(data) * cxDimsProd(nDim,dims,nDataVar)
          do i=1,num_bytes
             outDataArray(i) = inDataArray(i)
          end do
          return
          end
    c
    c     cleanup routine
    c
          integer function memclean(ptr,errcode,tstlat)
          implicit none
          integer ptr, errcode, tstlat, retcode
          if((ptr .eq. 0) .or. (errcode .ne. 0))then
             call cxDataRefDec(tstlat)
             memclean = 1
          else
             memclean = 0
          endif
          return
          end

### Test Module 2

This module reads in a tstLattice user-defined type and prints out some specific information from it. The code resides in /usr/explorer/src/UDTmods/print.c and /usr/explorer/src/UDTmods/print.f.

#### C Version:

    #include <cx/DataAccess.h>
    #include <cx/DataTypes.h>

    #include <cx/tstLattice.api.h>
    void prt( tstLattice *inlat )
    {
      char **f;
      tstData *outData;
      long i, nDV;
      float mt;
      cxErrorCode err;

      outData = tstLatticeDataStructureGet(inlat,&err);
      f = tstDataDataLabelsGet(outData,&err);
      nDV = tstDataNumDataVariablesGet(outData,&err);
      for(i=0;i<nDV;i++)
      {
        printf(“label %d = %s\n”,i,f[i]);
      }
      mt = tstDataEmptyGet(outData,&err);
      printf(“ Empty value = %f\n”,mt);
    }

#### Fortran Version:

        subroutine prt(inLat)
          integer inLat,outData
          real tstDataEmptyGet
          integer tstLatticeDataStructureGet
          integer tstDataNumDataVariablesGet
          outData = tstLatticeDataStructureGet(inLat,err)
    c
    c     there is no fortran call for getting an array of
    c     strings. So we cannot say
    c
    c      f = tstDataDataLabelsGet(outData,err)
    c
          nDV = tstDataNumDataVariablesGet(outData,err)
          empty = tstDataEmptyGet(outData,err)
          print *,” Empty value = “,empty
          return
          end

### Auto-generated Modules

There are two modules, ReadtstLattice and WritetstLattice, that allow the user to read and write ASCII or binary versions of the user-defined type. The code for these modules is auto-generated. The two routines are tstLatticeWrite and tstLatticeRead`.`

Prev | Table of Contents | Next
Chapter 7. Using the Parameter Data Type |  | Chapter 9. Advanced Module Writing