The PVT suite is implemented as a set of documentation files and source code files. This section describes the relationship among these files, and the format conventions they follow.
At a more abstract level, the PVT system can be viewed as a collection of objects of different types (PHIGS functions, PHIGS data structures, the PHIGS standard, etc.) and the relationships among them. The PVT diagram presents a schematic description of the major entities and relationships.
The PVT suite is organized in a hierarchical, or tree, structure. This tree closely resembles the organization of section 4 of the generic ISO PHIGS standard, which contains the basic conceptual description of PHIGS. See PVT Tree Structure for a complete description of the PVT tree.
Concretely, the tree structure is expressed by storing the modules of the PVT system in a hierarchical file directory, such as that supported by VAX/VMS, Unix, and MS-DOS. For illustrative purposes we shall adopt a neutral naming convention, wherein the levels of a file structure are separated by the "~" character, for example: PVT~05~03~DOC.TXT as the name of the documentation file for module 05~03. As distributed, the root of the PVT tree contains a number of system files:
There is at most one PVT module per node in the tree. Every
leaf node contains a module. A non-leaf node may contain a
module, or may be empty, serving simply to organize the system.
Every module contains exactly one documentation file,
conventionally named DOC.TXT, and one or more program files,
named p01.f, p02.f, etc. for Fortran,
or p01.c, etc. in the case of C. The documentation file contains the
set of semantic requirements (SRs) for the module, together with
the design for each of the module's programs. The programs
contain the test cases (TCs) for the module. Format details are
explained below. See Figure 1 for a schematic diagram of the
structure of a module.
Module
DOC.TXT (Documentation file)
Semantic Requirements
SR1
SR2
...
P01 design
P02 design
...
p01.f (Program file)
TC #1
TC #2
...
p02.f (Program file)
...
Figure 1: File Structure of a Module
The only other entities of the PVT system not in the root, besides the documentation and source code files, are the subroutine libraries (also in source code) used by the main programs. These libraries are in the tree structure, and are associated not with individual modules, but with the set of modules below them in the tree or at the same level. Thus, if programs in modules 05~01~01, 05~01~03, 05~02, and 05~04 all use a library of common subroutines, this library will reside in PVT~05. Libraries are named sublib.f or sublib.c. There is a global subroutine library, at the PVT level, containing routines used throughout the PVT system.
Finally, there are two special-purpose libraries, anomalously named trans-sublib and errlib in the root node. The former contains routines that simulate the geometric transformation utility functions of PHIGS and perform other mathematical functions. The latter contains the PVT-defined version of PERHND (the Fortran error handler) or perr_hand (the C error handler).
The subroutines are self-documented; each starts with a brief description of its purpose and of the meaning of its parameters. See Dictionary of Subroutines and Functions for the names and locations of all PVT subroutines.
There is never any order-dependence between modules, and usually there is no required order for program execution within a module. Following the PVT tree structure, depth-first, may be the most natural way to proceed through the modules, but there is no requirement to do so.
Module 11.02 is an exception to this rule: it checks the PHIGS header files for the C binding and its programs must be run in sequence.
Module documentation is written for a reasonably knowledgeable PHIGS user. Questions about PHIGS itself must be answered by reference to the standard [PHIGS89].
Figure 2 contains a schematic outline of the module documentation.
Refer to it, or to an actual example of module documentation
when reading this section.
TITLE:
MODULE#:
DESCRIPTION:
SEMANTIC REQUIREMENTS:
SR1:
#F functions
#D data structures
#S references to standard
#T test cases
#X module cross-references
#C comments
SR2:
#F
#D
#S
...
LOCAL DICTIONARY:
SEMANTIC CROSS-REFERENCES: (if applicable)
LOCAL SUBROUTINES: (if applicable)
PROGRAM 1
CHARACTERISTICS:
OPERATOR SCRIPT:
DESIGN:
TEST: #SR
"Text of test case message."
...
TEST: #SR
...
END PROGRAM 1
PROGRAM 2
...
END PROGRAM 2
...
Figure 2: Format of Module Documentation
The first heading, "TITLE", contains a brief phrase describing the main topic of the module. The second heading, "MODULE#" contains the identifying number of the module, which may be simply related to its location in the file hierarchy, e.g. the files of MODULE# 02.01.02 are in the PVT~02~01~02 sub-directory. All level numbers are two-digit. The third heading, "DESCRIPTION" contains a free-format paragraph explaining the scope of the module.
Next comes the list of semantic requirements associated with the PHIGS feature under test. These are assigned sequential identifying numbers, which will be referred to by the relevant TCs. Each SR is a simple declarative sentence, stating some requirement on the behavior of conforming implementations. Although intended to be clear and unambiguous, these have to be read in a "reasonable" way, with appropriate assumptions about context. The SRs are not "conditionalized" into absolute truths by explicitly stating all the normal pre-conditions that may apply (e.g. if a structure is open and if the system has space left to allocate, and if there is no power failure, then ...).
The SRs use the terminology of the standard when referring to functions or data structures. By convention, angle brackets are used when referring to the generic names of functions, e.g. <set element pointer>. Note that the words "valid" and "realizable" have a technical meaning: the former indicates simply that a parameter is accepted by a function without generating an error, while the latter indicates that the implementation must actually be able to render the graphical attribute in question. The wording of the SR usually applies to the generic standard, and is not language-dependent. Of course the SRs for the language specific modules are themselves language-dependent.
Immediately following each SR there are a number of associated fields, one per line, each prefaced by a "#". The first four of these, #F, #D, #S, and #T are mandatory.
#F is used to list the functions associated with this SR, i.e. those whose behavior is at least partially constrained by the SR. Functions are referred to by an identifying number (1-324) which corresponds to the order in which they appear in section 5 of the standard. The function table contains a complete description of function numbers.
The #D entry lists the data structures which are relevant to this SR, i.e. those whose contents are changed or inspected in the course of checking the SR. The data structures are referred to by an identifying hierarchical number which reflects the organization of section 6 of the standard. The data structure table contains a complete description of PHIGS data structures. Note that the data structure number need not be a leaf node. If the SR affects everything under a non-leaf node, then an entry for that node implicitly covers all those below it.
The #S entry lists those parts of the standard upon which the SR is logically based. These references are in the form: <section-number> / <page-number> / <paragraph-number>, all of which refer to the 1989 ANSI/ISO PHIGS standard, [PHIGS89]. The rules for counting paragraphs within a page of the standard are as follows. Whatever partial chunk of text is at the top of the page is number 1. Subsequent paragraphs are deemed to start by a blank line (even if half-height) followed by text at extreme left hand margin (not indented), but not counting section titles. Thus, page 38 has 7 paragraphs: 1 partial completing section 4.5.1, and 2-7 in section 4.5.2. Figures are associated with the preceding paragraph. For example, page 46 has 3 paragraphs with figure 7 as part of the first.
When an SR is based on one of the PHIGS language bindings, its #S entry is in the form <language> / <section-number> / <page-number> / <paragraph-number>, and refers to either [PHFOR90] or [PHC91].
The page numbers of the relevant functions, as listed under #F, are not included; if a function is relevant, it is to be assumed that its description in section 5 of the standard will have some bearing on the SR. If there is no basis in the standard for the SR besides the description of the relevant functions, an "n" appears in the #S entry.
Each SR, under the #T entry, lists the TCs which depend on it. The format of each TC reference is: P <program-number> / <test-case-number>. The test case number simply refers to its static sequential position in the text of the program. Note that not all TCs are always executed; under certain conditions specific to the implementation, some may be skipped. Moreover, some TCs occur in loops and are executed several times. Therefore the textual position of TCs in the code may not correspond to the executed order of TCs.
In some cases an SR may be significantly related to the topics of several modules. When this happens, we assign the SR to the most strongly related module, and use the #X entry to list the other relevant modules.
The #C entry is for free-form comments to allow explanation of any unusual aspect of the SR. For instance, if the support in the standard for the SR is obscure or indirect, the comment field might be used to explain the validity of the SR.
Since the #F and #D entries under the SRs are not self-explanatory, the documentation supplies the subset of the global function and data structure dictionary needed to decode the entries of this module.
If this module is pointed to by an entry in the #X field of another module, that pointer is noted here in the format <module-number>/SR<sr-number>. For example, if SR4 in module 02.01 contains a #X entry for module 04.01.01.01, then the latter module will have "02.01/SR4" as a semantic cross-reference entry.
If the programs of this module use any local subroutines (i.e. any besides those in the root-level global library), it is noted here. For detailed documentation of the logic and parameters of subroutines, please see the source code.
The second major part of the documentation of the module is a description of the programs and TCs which actually test the SRs of the module. The programs are numbered sequentially. Except for the language specific modules, the design is intended to be language-independent, relying only on the generic standard and not on language binding details.
Each program design starts with a "PROGRAM" header, which includes the ordinal number of the program within this module and a descriptive title. The next entry, "CHARACTERISTICS" contains a four-character code ("y" or "n" for yes or no) to indicate various properties of the program:
The CHARACTERISTICS are useful in setting up procedures to run the test suite. Programs with "--nn" are passive tests and may be run without operator intervention. Programs with "nn--" are workstation independent tests and need not be repeated when testing various workstation types (see section 4.1).
The last heading is "OPERATOR SCRIPT." This contains the instructions to be followed by the operator when running the test. If there is no need for operator intervention, then this entry will say "passive test." See section 4.4.5 for general operator instructions.
Each program design is terminated explicitly by an "END PROGRAM" heading, followed by the identifying ordinal number of the program.
The core of each program design is a body of pseudo-code which describes the flow of logic and data representation within the program. It should be clear from this pseudo-code why the embedded TCs are supposed to work. The pseudo-code describes only the logic of the program relevant to the TCs; incidental processing, such as opening PHIGS, or opening a structure is not included. The goal is to give the user an understanding of the basic logic of the code, not to depict all the programming details. For the latter, one can consult the code itself.
The style of the pseudo-code is meant to be informal and self-explanatory. Only a few common control structures are used, such as looping, if-then-else, and goto. Labels (the object of goto statements) begin in column 1 and terminate with a colon. The heading "TEST:" heralds the beginning of a test case. This is followed on the same line by "#SR" and then a list of the SRs upon which this TC is logically based. Beginning on the next line is the textual statement of the expected (correct) result of the test, surrounded by double-quotes. The text is unique within the program and thus serves as the identifier of the TC.
For interactive tests, prompts to the operator are indicated by "OPQA" (for operator question and answer), followed by a slash, a topical heading in upper case, and then the question itself, e.g:
OPQA/EDGE FLAG INDICATOR: Which triangles have visible edges?
The result of each test is recorded by executing either a "pass" or "fail" procedure, denoted in the pseudo-code simply by those words. Every TC should cause execution of either one or the other (but not both, of course) of these. In many cases, where the result depends directly on a single condition, instead of coding:
if (condition) then
pass
else
fail
endif
we use the short-hand form:
pass/fail depending on (condition)
In this section we discuss characteristics which pertain to the source code of the PVT system. The code itself is written to be comprehensible by a PHIGS- and Fortran-literate reader. Since the C version of the PVT is generated automatically from the Fortran version, it is somewhat less readable. You may wish to use the corresponding pseudo-code or Fortran code as an aide to understanding the C code.
The language of version 2.1 of the PVT system is full ANSI Fortran (ANSI X3.9-1978) as defined in [FORT78] or ANSI C (ANSI X3.159-1989) as defined in [C1989]. The language binding to PHIGS is the full Fortran binding [PHFOR90] or C binding [PHC91].
The most desirable approach for writing a C binding test suite would have been to start with the design document for each program, and have written new C code to perform the tests outlined. However, due to the limited resources available to the NIST, a Fortran to C translator was used to produce the C binding test suite.
Using the translator meant starting with well-designed and tested Fortran code, and using that code to generate the C binding code. All of the design and programming effort was expended on the Fortran code are carried over to the C binding test suite through the translator with minimal effort.
The translation utility used was the public domain f2c (Fortran to C) converter, developed by AT&T and Bellcore labs. After evaluating several proprietary and other public domain packages, this converter was chosen for two reasons:
The directory structure of the f2c utility was altered slightly
to decrease the number of libraries created. The libraries libF77 and
libI77 have been combined into a single library: libf2c. Since all of
the code has been translated, the translation utility f2c itself is
not contained in the distribution. The directory pvt/F2C contains the
source code for the subroutine library (see Figure 3). Since the
code has already been translated, only the subroutine library (i.e.
libf2c) need be present to link the programs.
Figure 3: PVT Structure for C binding
The f2c source code itself has not been altered in any way, and sites
that have f2c installed as a system library may use their own version.
Once the Fortran code was translated by the f2c utility, the generated C code still calls the PHIGS Fortran library. To interface this translated code to the PHIGS C library, a "layer" of C code was written between the translated test code and the PHIGS C library (see figure 3). Each PHIGS Fortran routine has an equivalent layer routine where the first letter has been changed to an "n" (e.g. the PHIGS routine ppl becomes the layer routine npl, and is contained in the file npl.c within the LAYER sub-directory). Each subroutine in the layer is documented with both the FORTRAN and the C parameters and their meanings. Within the generated C code, references to Fortran/PHIGS routines, which all begin with "P", are automatically altered to begin with "n".
The layer routine accepts the Fortran input parameters and converts them into the equivalent C structures required by the C binding. The equivalent PHIGS C library call is then made (e.g. ppolyline for ppl), allocating space if necessary. The output parameters (if any) from the PHIGS C library call are then extracted and returned in the Fortran output parameters. The routine also frees any space it previously allocated.
This approach was possible since the standard requires the same information to be present for each function, regardless of the binding. Therefore, even though the syntax of the two calls differ, they both process the same information.
Although not required by the Fortran standard, the code explicitly declares the type of all program variables. We believe this is useful for several purposes: it helps avoid certain programming errors (e.g. misspelled variables can be detected by having the compiler flag undeclared variables) and enhances self-documentation of the code.
Where appropriate, the Fortran code uses the standard symbolic constant names recommended in section 6 (Enumeration Types) of the Fortran binding standard [PHFOR90]. Since the C code is generated from the Fortran, the standard C macro definitions in section 6 of [PHC91] are not used. For example, the linetype dashed is denoted by PLDASH, not by PLINE_DASH.
When the code invokes a PHIGS function some of whose output parameters are not used in the subsequent logic, these parameters are assigned names in the format: <type>DUM<digit>, where <type> is "I" for integer, "R" for real, "L" for logical, and "C" for character, and where <digit> is some differentiating digit. This tells the reader which parameters are relevant to the logic of the program and which are incidental.
Each test program starts with comment lines forming a banner. This banner identifies the program with a unique "TEST NUMBER" and a "TEST TITLE". These appear in a box of asterisks. The format of the test number is <module-number> / <program-number>. For example, "04.03.01/02" identifies program number 2 in module 04.03.01. Thus, the program's file name is p02.f or p02.c in directory PVT~04~03~01.
Each test program contains a set of identical declarations for certain variables in the common areas GLOBNU and GLOBCH. These are used to convey information within the system. For a full description of each of these global variables, see Appendix: Global Variables.
This section describes some of the conventions adopted for the C version of the PVT. A major constraint was the need to be compatible with the code produced by the f2c translator.
Prototyping is a mechanism by which the expected parameter types for an invoked function can be declared and checked against actual usage. Prototyping is allowed by the C language standard and all code generated will, by default, use it [C1989]. However, for those compilers that do not support prototyping, this can be suppressed (see section 3.2.10, below).
In PHIGS, a file, as an input parameter, appears in <open phigs>, <open archive file>, <error handler>, <error logging>, and as an output parameter, in <inquire open archive file>. The PHIGS FORTRAN binding assigns the filename to the type integer, and in most instances is associated with a logical unit number. The PHIGS C binding, however, assigns the filename to a character string.
FORTRAN addresses files using the integer unit number with which the file was opened. The C language, on the other hand, addresses a file by the file pointer with which it was opened. These two types differ greatly and there is no one-to-one correspondence between them. The C tests must use a table that is kept by the f2c code which keeps track of FORTRAN unit numbers and the C files they represent. A number of the layer code routines include the f2c header file "fio.h". These routines deal with file handling and must search the file table to match a FORTRAN unit number to a C filename. An array (of default size 100) is created at the start of each program. Array index [i] contains the information on logical unit i (e.g. array[10] contains the information on logical unit 10). This information should not affect any routine provided in the test suite, for they correctly handle each situation.
The FORTRAN binding has two functions, pack and unpack, which convert data from arrays of integer, real and character data to and from an array of 80 character records. This latter array, in turn, may be used as a parameter to subsequent PHIGS calls. However, the C binding has no such functions (it uses large structures to accomplish this same purpose). Therefore, the layer code for pack and unpack does not encode the data into the character arrays. Rather, it defines a general purpose type (see struct.h in pvt/LAYER) to hold such data.
The layer code for pack and unpack (nprec and nurec) will move data into and from the input arrays to a structure defined in struct.h, contained in the LAYER directory. Since NIST is coding pack and unpack (not the implementor), the format for data records is set by us. The current approach is to overlay a structure (special typedef) which holds 20 integers, 20 reals, and 5 strings on top of the raw 80xN area defined in FORTRAN. This method requires that 592 bytes of storage space be available, resulting in the minimum datrec declaration being 80 x 8. Since the test code adheres to this requirement and the method simulates the FORTRAN implementation exactly, the data storage will be totally transparent to the user.
The layer code for functions having input parameters of type data-record must be aware of the datrec format and use it correctly. These functions then face only the familiar problem of re-formatting FORTRAN style data into C style data to be passed to the equivalent C functions. The only difference is that in this case, NIST defined the FORTRAN-style data, instead of the PHIGS standard. This makes sense, since the FORTRAN binding specifically does not define the internal format of data-record - it mandates only that pack stores the data, and that unpack is capable of retrieving the same data.
Using the C language, this new data structure is defined as a parameter to each of the functions that use it, instead of declaring it as the FORTRAN 80xN array of characters. Since the FORTRAN main program does not manipulate these arrays in any way (it only passes them to the subroutines), the actual content of the array is never known by the main program. The subroutines, however, receive the array as a pointer to a structure (defined in struct.h). The data from the arrays is stored in the structure and the array passed back. This array is passed by the main program to one of the PHIGS functions that uses it. Each of those functions again declares the received array as type pointer to structure (e.g. a structure pointer), and interprets the structure the same way it was packed, allowing the data to be retrieved and used. There is overhead in this method since FORTRAN requires two steps (pack data, use data), and C requires only one (use data). The C code must therefore emulate the FORTRAN code to limit the amount of changes that must be performed by hand on translated code.
Character strings are represented differently in the FORTRAN language than the C language. In FORTRAN, the length of a character string is declared as a property of the variable itself. In C, a string can be of any length and a null termination character signals the end of the string. The f2c translator, therefore, represents each FORTRAN character variable as two C data types. The first is the length of the string, and the second is the string itself. Each subroutine call in FORTRAN that contains a character variable is translated into a C function call with an extra parameter for each character variable added on to the end. The lengths of each string are conveyed in these extra variables. The layer code is written to account for these length variables. Therefore, in the LAYER code, some PHIGS functions have more parameters than the standard requires. These parameters are added to hold the lengths of the strings to be received. For example the FORTRAN call to <text>:
REAL PX, PY
CHARACTER*(*) CHARS
...
CALL PTX ( PX, PY, CHARS )
is translated to:
float *px, *py;
char *chars;
int clen;
...
ntx (*px, *py, *chars, clen)
Notice the extra parameter clen. This variable is added to convey the length of the character string chars. Each routine that uses strings follows this convention of the translator. Since the length is known, the layer code checks for variables that are too small to hold data returned from inquires, and will return the PHIGS error 2001 (Ignoring function, output parameter size insufficient) if they are found.
The FORTRAN standard defines the subroutine PERHND as the name of the error handling routine. A user can write his own error handling subroutine, but must call it PERHND. To have this routine invoked, most implementations require the user to link his PERHND routine before the system routines. The C standard defines the function perr_hand to perform the equivalent error handling function.
The Fortran test code makes extensive use of the NIST-defined PERHND routine to determine if a failure is so great that the test code must abort immediately. In order to preserve this functionality, the following steps were taken.
This procedure allows invocation of the error handler in C, either by the implementation's error signalling or by explicit calls to PERHND in the Fortran code and also supports both user-defined and implementor-defined error handling.
Another error handling issue is the PHIGS/C function pset_err_hand. This function designates an alternate routine (other than the system default perr_hand) to be used as the error handler. It has no equivalent in Fortran and is tested in the C-specific module 09.01.11.01.
All of these changes should be totally transparent to the user and are described here only for the purpose of clarifying the code.
Another common problem encountered when changing between languages is the way parameters to functions are passed. FORTRAN parameters are passed by reference or value, depending on whether the parameter is a variable or some other expression. The FORTRAN standard states that constants (e.g. 3), expressions (e.g. X+2) and constant expressions (i.e. variables declared as parameters), declared as parameters to functions, may not be changed in those functions. This would require parameters to be passed by value. However, it is not specified that they cannot be passed by reference. To avoid this problem, the translator passes all variables by reference. To prevent problems with constants and expressions, they are first assigned to a newly created temporary variable, and then the address of that variable is passed to the function. As an example:
CALL SUB (3, X, X+3) { value, reference, value }
is translated to:
int c_1, c_2;
c_1 = 3;
c_2 = X + 3;
sub(&c_1, &x, &c_2);
Arrays and regular variables are already passed by reference, and there is no distinction between input or output variables. The layer code receives all variables as pointers.
The last problem encountered dealt with array indexing which is the result of another difference between the C and FORTRAN languages, namely the way they physically store arrays. The FORTRAN standard specifies column-major order (i.e. first index is least significant) when physically storing arrays in memory. C, on the other hand, specifies row-major order (first index most significant) when storing the arrays. If a FORTRAN program were to pass a multi-dimensional array to a C function, the array would have to be first transposed. Then, the array is used by the C routines, and again transposed before being passed back to the FORTRAN program. The translator emulates the physical storage of the FORTRAN code by collapsing all n-dimensional arrays to a single dimension, and calculating array indexes internally. The layer code performs all necessary transpositions when building the equivalent C structure.
In this section, we discuss briefly the function of some of the more commonly-used subroutines. Since these are used throughout the PVT system, anyone wishing to understand the code should be familiar with their purpose. There are many other subroutines, however. All PVT subroutine libraries are self-documented, so when questions arise, the code itself should be consulted.
Normally, the first executable statement within a program will be a call to INITGL. This routine performs all the work needed to set up the environment for the program; in particular, it initializes the values in the common areas GLOBNU and GLOBCH so that they may be used freely (e.g. workstation type needed to open a workstation). It does this by reading certain constant values from the PVT configuration file (see section 4.2 on Running INITPH). The program supplies its identifying TEST NUMBER (see section above on Banner) as the single parameter.
For interactive programs, the SETDLG subroutine sets up the dialogue area as requested in the configuration file, and initializes the DIALOG common area.
The last executable statement is a call to ENDIT. This closes all open workstations, closes the currently open structure, and closes PHIGS. It then calls WINDUP to write out summary results, close files, and perform any other processing needed to finish the test program.
There are a number of subroutines needed to implement a given TC in the code. These correspond closely to the way a TC is set up in the pseudo-code. The SETMSG subroutine sets up a "current TC message" for the condition about to be tested, which contains both the SR references and the text describing the condition under test. If a test case is currently being checked for adherence to the standard, the test message will include "[UNDER REVIEW]".
Depending on the results of the TC, the program then executes either the PASS or FAIL subroutine, which records the result. The IFPF subroutine is a shorthand form, which accepts a single logical expression as a parameter and invokes PASS if it evaluates as true, and FAIL if false.
The only persistent output of each test program is a series of messages. The following subroutines generate messages of the indicated type:
Subroutine Message-type Function
---------- ------------ --------
INITGL SY initialize program
ENDIT SY finalize program
PASS OK record TC passed
FAIL FA record TC failed
IFPF OK or FA record TC result
INMSG IN information
UNMSG UN abort
NCMSG NC abort
CHKINQ NC continue or abort
See section 4.4.6 on for the interpretation of messages and message-types. The subroutines UNMSG and NCMSG are used when the program must be aborted, as opposed to normal program conclusion which is done via ENDIT. No further code is executed after either of these is encountered in the flow of control.
It is very common within the PVT system for an inquire function to be incidental to the main purpose of the test. Since the test relies on the result of the inquire, we wish to ensure that it has completed successfully. Every incidental use of an inquire, therefore, is followed by CHKINQ, which simply checks that the error indicator from the function is zero. If not, CHKINQ invokes NCMSG and therefore aborts the program. Otherwise there is no effect.
Of course, when an inquire function is being purposefully tested, its error indicator is checked explicitly as part of the usual PASS/FAIL determination.
Previous section is Section 1: Table Of Contents And Introduction. Next section is Section 3: Installation.