|
|
The most noticeable difference between G2 and G2++ is the radically different appearance of the G2++ programming language interface. To illustrate the interface, we will build the G2++ equivalent to the client program developed in the last section, ``Adding Builtin C Types to the Repertoire of G2++ Types''. We start by compiling the identical usr.g file, but this time we use the G2++ compiler g2++comp(CP):
$ g2++comp usr.g
to which g2++comp(CP) responds:
usr.g: =>usr.[hc]
indicating, once again, that two files have been created: usr.h and usr.c.
The fact that we compiled the identical usr.g file illustrates an important point: the G2++ record definition language is a strict superset of the G2 language; we will look at G2++ language extensions later in this paper, but for now, suffice it to say that the common subset has identical semantics in both languages. This means that a G2++ program and a G2 program that use the same record definition cannot be told apart by purely external means.
The usr.h produced by g2++comp(CP) contains a somewhat different- looking structure definition for type USR:
usr.h -- generated by g2++comp(CP) #include <String.h> #include <Vblock.h>
typedef struct USR{ String login; struct{ long usr; short grp; }id; String name; Vblock<long> proj; USR(); }USR; ostream& operator<<(ostream& os, const USR& x); istream& operator>>(istream& is, USR& x);
Next, we write the client C++ program:
main.c - C++ program #include <g2++.h> #include "usr.h" main(){ String name;
while( name=g2seek(cin) ){ if( name == "usr" ){ USR u; cin >> u; u.id.grp += 100; cout << u; }else{ G2BUF b; cin >> b; cout << b; } } }
Finally, we compile and link this program together with the .c file produced by the G2++ compiler, plus I/O routines from the library:
$ CC main.c usr.c -l++
Perhaps the most surprising thing about the C++ version of main.c is that it appears to use only three I/O routines (as compared with five for the C program):
g2seek(cin); Find the next record and return its name cin >> x; Extract a record from cin into x cout << x; Insert a record from x into cout
where x may be either of type G2BUF or type USR. The right shift and left shift operators, called extractors and inserters, respectively, are actually overloaded:
Untyped inserters and extractors do the work of the G2 functions putbuf(), getbuf(), and getbuf1() (functions that constitute what Weythman calls G2's ``interpreted interface''). The untyped inserters and extractors are declared in g2++.h; their behavior is specified in untyped_io(C++).
The function g2seek() searches a stream for a G2++ record and returns the name of the record. The function comes in two overloaded versions:
g2seek(cin) Scan the input stream for the next record and return its name. g2seek(cin,"usr") Scan the input stream for the next ``usr'' record
Following a call to g2seek(), the client is free to extract the record or ignore it entirely. The operator used to extract the record may be typed or untyped, depending on how the client wishes to treat the record. g2seek() is similar to the G2 function getname(). g2seek() is declared in g2++.h and specified along with the untyped inserters and extractors in untyped_io(C++).
Let us continue with the example. After a typed extraction, each member of the structure u will contain a value obtained from the input record (or the appropriate null value if the corresponding field was missing from the record). The client program can manipulate a given member using operations applicable to objects of the member's type. For example, an integer member can be incremented. After an untyped extraction, the nodes of the structure b will be populated with the ASCII field names and values from the input record, and can be navigated by the client program. There is currently no provision in untyped_io(C++) for altering the structure of a syntax tree; trees may only be navigated by following root, child and next pointers.
For example, suppose that the standard input contains a person record followed by a usr record. Tabs are used for indentation and also to separate field names from their corresponding values.
person name Bob age 11 hobbies 0 swimming 1 tennis
usr login jrd id usr 129 grp 159 name J.R. Dobbs logdir /usr/bob shell /usr/tools/bin/ksh
The first pass through the loop will result in an untyped extraction, creating the following value in b:
The second pass through the loop will result in a typed extraction, creating the following value of u:
Having just seen two functionally equivalent programs, don't conclude that G2++ is mere syntactic ``sugar coating'' on G2. The differences between G2 and G2++ are quite significant, as we hope to show in the remainder of this tutorial.