|
|
Suppose that we want to extend usr records by adding a field of some user-defined type. The type of interest may already be defined in an existing header file; or, it may be a new type that we have yet to define. As we will see, there are only two unavoidable restrictions that G2++ imposes on a user-defined type: (1) the type must have an assignment operator The assignment operator is used by the extractor to assign values to structure members of that type. (2) if the type occurs in an array, it must have a parameterless constructor. This restriction is inherited from C++.
For example, suppose that we decide to add a field named last_login of type Time(3C++) to usr records. g2++comp(1C++) requires that a user-defined type be explicitly declared with the keyword USER prior to its first use in a record definition. Here, then, is the simplest .g file that will compile without error:
usr.g Time USER usr login 6 last_login Time id usr LONG grp SHORT name 20 proj 4 LONG
Given only the information that Time is a user-defined type, g2++comp(1C++) will be forced to make a few assumptions. First, it will assume that there is a file named Time.h that defines a type named Time (this assumption happens to be correct). Accordingly, it generates the following header file:
usr.h #include "Time.h" typedef struct USR{ ... Time last_login; ... }USR;
g2++comp(1C++) will also assume that Time.h declares inserters and extractors capable of writing or reading external representations of Time values to or from an ostream(3C++) or an istream(3C++), respectively. These operators are needed because the USR inserter and extractor delegate insertion and extraction of Time values to them:
USR u; cout << u; delegates insertion of last_login field to Time::operator<< cin >> u; delegates extraction of last_login field to Time::operator>>
This last assumption is not correct, at least insofar as extraction is concerned, but let us continue anyway. Other assumptions made by g2++comp(1C++) in the current example include:
usr.g Time USER .header Time.h .header Timeio.h .null Time::MIN ...
We will address the specific attributes used in the above example later. First, let's look at the general form of a USER type definition:
USER .header H1 .header H2 ... .null N .isnull I .put P .get G
The significance of each of the attributes is explained below:
int I(const & t);
This function is expected to return 1 if its argument is null, and 0 otherwise. If the isnull attribute is omitted, g2++comp(1C++) will generate code to explicitly test for equality with the null value defined by the null attribute; this means that omitting the isnull attribute implies a contract that there exists, somewhere in the header file closure implied by header attributes, the declaration of an equality operator for type .
ostream& P(ostream& os,const & t);
P is expected to insert an external representation of t into stream os. To preserve the integrity of the record, the external representation must not contain tabs, newlines, or other nonprintable ASCII characters.
If the put attribute is omitted, g2++comp(1C++) will call ::operator<< to do the insertion. This means that omitting the put attribute implies a contract that there exists, somewhere in the header file closure implied by header attributes, the declaration of an inserter for type .
istream& G(istream& is, & t);
G is expected to extract an external representation from stream is, construct an object of type , and assign it to t. The function must extract only the characters constituting the external representation and leave the stream positioned so that the first character extracted by a subsequent extraction will be the first character following the external representation of type . If G cannot construct an object of type , it should assign a null value to t and clear the error bits (see ios(3C++)). If the get attribute is omitted, g2++comp(1C++) will call ::operator>> to do the extraction. This means that omitting the get attribute implies a contract that there exists, somewhere in the header file closure implied by header attributes, the declaration of an extractor for type .
We cannot overemphasize that each piece of information furnished via an attribute (or implied by omitting an attribute) merely creates a contract between the record definer and the software developers responsible for providing the named or implied facilities. g2++comp(1C++) cannot check or enforce these contracts. For example, it does not check for the existence of files named in header attributes, nor does it typecheck any expressions in null attributes. It is only later, when the application is compiled and linked, that the named or implied facilities must actually exist. This delayed binding allows applications to be developed in parallel with their infrastructure.