8. SYNTAX

8.1 Includes and Defines
8.2 ZZ_EXT
8.3 ZZ_ORG
8.4 ZZ_HYPER
8.5 ZZ_LOCAL
8.6 Other ZZ Calls
8.7 Version of this Library
8.8 CMP() and Other Auxillary Functions

8.1 INCLUDES AND DEFINES

8.1.1 ZZmain

DOL uses only a few global variables, and #define ZZmain designates the file where these variables will be declared. This often is the file with the main() program, but it does not have to be. Simply select one of your files to be the main DOL file. If your program is all in one file, you need #define ZZmain.

If several programmers use DOL in a hierarchical fashion, as described in Chap.18, only one file can have ZZmain. Using ZZmain in more than one file will cause the linker to complain about multiply declared variables.

See also ZZlocal below.

8.1.2 zzincl.h and zzfunc.c

The DOL class generator creates two files, with which you have to compile your source. The default names for these files are zzincl.h and zzfunc.c. Your program usually starts with #include "zzincl.h.", and has #include "zzfunc.c" at the end of one of your files, or compiles zzfunc.c separately.

Note that zzincl.h also contains, besides other include directives, the statement #include "../zzcomb.h.", which includes the whole macro library.

zzincl.h (not zzfunc.c) is protected against multiple inclusion. Simply, you can use it everywhere without worrying about multiple declarations.

If you want to choose different names for these files, avoid using the prefix ZZ, which is reserved for DOL variables and functions. The general syntax for calling the class generator is:

orgCpath/zzprep prog.c <incl.h> <zzmaster> <func.c>

where prog.c is the file with your program, and incl.h and func.c are the files equivalent to zzincl.h and zzfunc.c, and master is the master-control file of your macro library, usually zzmaster.

zzprep must be given a full or relative path to the orgC directory; other files are given without the path. incl.h and func.c will automatically be in the current directory, master in the macro directory under the directory where zzprep is (typically orgC/macro).

If your program consists of several files, you have two options:

Either run the class generator only on your header file, which contains the declarations of all objects and their organizations, or concatenate all your files together and run them through the class generator.

File zzincl.h automatically pulls in stdio.h. Most examples in the test directory include stdio.h explicitely, as it was needed in OrgC/C++ prior to version 3.0. Today it is not necessary.

If you use VisualC++ from Microsoft, note that file zzincl.h must be included after stdafx.h:

#include <stdafx.>
#include "zzincl.h"

8.1.3 ZZimplicit

ZZimplicit is one of the important defines in the C version of the library (Data Manager for C, OrgC), but it is not used in DOL.

8.1.4 ZZselectMacros

When delivering a finished software product, it is not necessary to send the whole macro library (zzcomb.h) with it. If #define ZZselectMacros is used, the class generator selects those macros that are actually used by your program, and places them into the file zzcomb.h in your current(!) directory. File zzincl.h is then instructed to pick up that file, and not zzcomb.h from the main OrgC directory.

ZZselectMacros causes a relatively long class generator run, makes shorter include files, and slightly shorter compilation, with no effect on run-time. It is a good idea to develop a program without this #define, and add it only just before compiling the final version of the software.

8.1.6. ZZascii

Normally, when saving data to disk, binary format is used. For ASCII format, (also see Chap.13.2), use #define ZZascii. This define must also be used when using both formats within the same program - see macro ZZ_MODE_SAVE(). As an example, compare test16a.c (saving in binary format) with test16b.c (saving in ASCII format).

When using the interactive browser, this define must be used, because the browser is printing objects in the ASCII format on the screen.

8.1.7. ZZcplus

When running with C++, #define ZZcplus must always be present in your lib/environ.h file. For example, when using ZORTECH C++ the file should contain, and possibly other defines, the following statements:

#define DOS
#define ZORTECH
#define ZZcplus

The class generator automatically creates necessary friend declarations and inserts them under the appropriate ZZ_EXT statements. The generation of friends may be disabled by using

#define ZZnoFriends.

This may be useful if running C++ in C-like style, with structures instead of classes.

If you are running with C++ Ver.2.1 or higher, declare ZZcplus21 instead of ZZcplus. For example, when running C++ Ver.3.0 on Sun, you need:

#define UNIX
#define SUN3_0
#define ZZcplus21
#define ZZansi
#define ZZ_INHERIT

8.1.8 ZZ_INHERIT and ZZinheritAll

When you use DOL with C++, you must define either ZZ_INHERIT or ZZinheritAll. The only situation when you do not need these two defines is when your entire design is based on simple C-like classes called lightweight classes. Lightweight classes neither inherit nor include member objects. For example:


    class A {  // would be lightweigh by itself
        int i;
        A *ap; // pointer, not an object
        ...
    };

    class B : A { // not lightweight, inheritance
        ...
    };

    class C { // not lightweight
        A a;  // object, not a pointer
        ...
    };

When you have even a single class which is not lightweight, you must define either ZZ_INHERIT or ZZinheritAll. If you don't, after saving your objects to disk they will not be restored properly. But why do we have two options?

ZZinheritAll requires no other considerations and it guarantees that all the general situations that can happen in C++ are properly handled, including multiple inheritance and pointers that lead inside other objects. If you are starting with DOL or if you don't care much about the memory and disk footprints, this is the define you should use.

If you want the most efficient data storage and speed, you should use ZZ_INHERIT. However, this option requires that all base classes have at least one virtual function. This normally is the case (why would you use inheritance without virtual functions?), but if it isn't the case you have to add a dummy virtual function:


    // base class with a virtual function(s)
    class A {  
        int i;
        virtual void prt(){printf("%d",i);}
        ...
    };

    class B : A { 
        ...
    };

    class C {  // base class without virtual functions
        int i;
        virtual void foo(){} // added dummy function
        ...
    };

    class D : C { 
        ...
    };

The advantage of using ZZ_INHERIT is a smaller footprint both in memory and on disk, and a slightly faster data access. Objects of lightweight classes are 8 bytes smaller compared to using ZZinheritAll. Classes involved in inheritance have the same size.

The main reason for using ZZinheritAll is the safety of maintenance. When adding a new class to an existing design using ZZ_INHERIT, it is easy to forget that the base class must have a virtual function. The program seems to save and restore data from the disk (there is not crash or error messages), and it may take some time to discover that members of the derived class are not saved correctly.

IMPORTANT NOTES:

(1) Everything what we discussed about ZZ_INHERIT and ZZinheritAll applies to saving data in the ASCII or binary modes. When saving data with memory blasting, you don't have to worry about the presence of virtual functions, and ZZ_INHERIT is the better choice.

(2) If you have only lightweight classes and, for the sake of performance, you decide to use neither ZZ_INHERIT nor ZZinheritAll, all classes with ZZ_EXT_ must have a void constructor, and all constructors must call either ZZ_INIT() or ZZ_OBJECT_ALLOC(). For examples, see test11a.c or test11b.c.

    

SUMMARY:

(a) ZZinheritAll - always works, but may use more memory and disk (b) ZZ_INHERIT - clear choice for memory blasting; for ASCII/binary modes base classes must have a virtual function (c) neither used - lightweight classes only, special rule about constructors
All environment files in orgc/lib use ZZ_INHERIT. Change it to ZZinheritAll, if you want to play it safe.

Test programs orgc/test/test60*.c illustrate all what has been discussed in this chapter:

test60a.c shows the situation where the dummy virtual function must be added (binary saving mode, ZZ_INHERIT, no virtual function used by class ShapeType).
test60b.c shows the same program using ZZinheritAll (no dummy virtual function is needed).
test60c.c shows the same program coded in a better style - with ZZ_INHERIT and natural virtual functions. The disk size is 1,158 bytes compared to 1,238 for test60b.
test60d.c shows test60a when saving in the memory blasting mode. The dummy virtual function is not needed.

8.1.9 ZZansi

This define must be used in the environ.h file, if you run with a compiler that adheres to the ANSI standard. This include is required for most C++ compilers.

Except for this one define, do not use the word ZZansi anywhere in your code. If you want to differentiate between ANSI and non-ANSI code, use

#ifdef ZZ_ANSI.

8.1.10 ZZmultiProj

This define must be used in the main header file of a multi-project, where DOL is used hierarchically by several programmers or projects (see an example in directory orgC/test/multi). Normally, only one set of internal type tables is kept by DOL, but in the case of a multi-project, type tables are different at each level. In this case, DOL uses tables which are static. For this reason in a multi-project design, larger files with more functions in them are more memory efficient than individual files for each small function. Note that in the multi-project example supplied in the orgC/test/multi directory, -ZZmultiProj is needed only in the file orgC/test/multi/main/proj.h.

8.1.11 ZZnoCheck

This define disables run-time checking. The use of this define is potentially dangerous, but it makes some sense in two situations:

  1. When trying to speed-up a well debugged version of the code;
  2. when trying to avoid error messages caused by discarding automatically allocated objects.

For example, when leaving the following function

void foo(int i){ Block b; ... }

a C++ compiler automatically deallocates b. If b had been used in some data organizations and had not been explicitely disconnected, this results in an DOL error message.

If the data organization, to which b had been connected, was only temporary, then disconnecting it is a waste of time. If all this is not intentional, however, using ZZnoCheck will leave a substantial bug hidden in the code.

In this version, some commands are not sensitive to this option. You can update the files yourself, however, by adding appropriate if-statements inside the macros. For an example, see macro/delname or macro/delslink.

8.1.12 ZZ_NOLEAK

SUN compilers use a very inefficient (quadratic) deallocator, which is very slow especially if many small objects are being freed. For this reason, the default of OrgC/C++ on SUN is not to free any objects or temporary structures. If you are concerned more about memory than about the time your program needs to run, please specify #define ZZ_NOLEAK.

For platforms other than SUN, ZZ_NOLEAK is the default.

8.1.13 ZZlocal

This define replaces ZZmain for a subproject, which is using OrgC/C++ in parallel, but independently from other subprojects. Saving to disk is not permitted on programs running in this mode.

For example, the PAGER organization in orgC/lib/pager.cc is coded with OrgC/C++, and though it is a part of the library, it does not interfere with your application code. #define ZZlocal forces global declarations to be static.

HINT: If you have several independent subprojects, none of them saves data to disk, all can be coded in (or concatenated into) one file, and you want them to be coded in a uniform style, then make this change inside the library: Edit file orgc/lib/pager.cc, find the line with #define ZZlocal, replace ZZlocal by ZZmain, and recompile the library (for example by executing msft.bat.

8.1.14 ZZnoDestr

From Version 3.01, void constructors/destructors containing ZZ_INIT() and ZZ_CHECK() are generated automatically. That means that automatically allocated objects are also automatically checked for disconnection before exiting from any function. In some programs, which generate a lot of data without taking care of how to dispose of it on the exit from the program, this full checking generates a lot of error messages, which are irrelevant to what the programmer wants to do. #define ZZnoDestr blocks the automatic generation of destructors.

For an example of how to use ZZnoDestr together with multiple inheritance, see test28.c

8.1.15 ZZbreakLine

This statement is not a define, but a special mark to be used to split long files that must run through zzprep into smaller, independently processed segments. Normally, zzprep

reads all its input into memory, and processes it as one big string. For large projects, this may be a problem under DOS, where heap data is restricted to under 64k. When ZZbreakLine statements are inserted, processing proceeds section by section. This slightly slows processing, but avoids the size limitation.

ZZbreakLine statements must be on a separate line, and start right from the beginning of the line. They also must not split classes or functions. If you want to split the input into many small segments, please use one ZZbreakLine statement after each class declaration.

Example: test25c.c

8.2 ZZ_EXT_..

For each class which is involved in one or more organizations, you have to use one (and only one) ZZ_EXT_.. statement. The ending of this statement must exactly match the class name. When running with C++, use classes, not structures.

Examples:

class Plum {
    ZZ_EXT_Plum
    float weight;
public:
    ...
};
class Apple {
    int size;
    ZZ_EXT_Apple
    char colour;
public:
    ...
 };

The ZZ_EXT statements instruct the class generator on where to insert automatic pointers, that are otherwise transparent. In C++, this statement also inserts the declaration of friends for all related objects. Note that the semicolon must not be used after the ZZ_EXT_ statement. These statements can be anywhere within the structure, but if the SELF_ID organization is used on the object, ZZ_EXT must be at the beginning of the structure. If this condition is not met, the program detects it at run-time, and prints an error message.

When hyper-objects are used (ZZ_HYPER_...), ZZ_EXT_ should be in the private part of the class.

ZZ_EXT may change private into public, therefore a new private statement must follow after ZZ_EXT, if the following variables are to be private.

8.3 ZZ_ORG

These statements are used to declare organizations in C, or in C++ when hyper-objects are not used (Data Manager for C, OrgC, R book), and have a similar purpose to that of ZZ_HYPER statements (see below). For example, the statements

ZZ_ORG_SINGLE_RING(eRing,Employee);
ZZ_ORG_TIME_STAMP(Employee);
ZZ_ORG_HASH(eTable,Header,Employee);

are a direct equivalent of

ZZ_HYPER_SINGLE_RING(eRing,Employee);
ZZ_HYPER_TIME_STAMP(Employee);
ZZ_HYPER_HASH(eTable,Header,Employee);

The syntax is exactly the same, but the internal action is totally different. ZZ_ORG_ is only an instruction for the class generator, and is translated as a comment. ZZ_HYPER hides a whole class declaration, including one instance of that class (id).

This is only for your general information, you should not use ZZ_ORG_ with this tool.

8.4 ZZ_HYPER

ZZ_HYPER statements typically follow object declarations, and declare a hyper-class with one instance of the object. They are equivalent to a database schema, and describe the relations between individual data types. These statements can also be interpreted as instance declarations of abstract organizations.

The table-like form of ZZ_HYPER statements and their clarity are important features of DOL.

The syntax of the ZZ_HYPER statement is:

ZZ_HYPER_organization(id,type1,type2,..)

where

organization is one of the organizations from the DOL library; id is the instance name of this organization, and type1, type2, ... specify the types to be used.

Each organization has a specific number of types.

Some special hardwired organizations such as SELF_ID, PROPERTY, or TIME_STAMP do not require the id, because there can be only one instance of such organizations in any object type.

Otherwise, there is no limit on the number of organizations which are used for any object type. There is also no limit on the number of types used in an organization; in the present library, however, there are no organizations with more than 3 types.

Example 1:

We have four objects: State, Town, Highway, and AirLink. Towns are connected by two networks: Highways and AirLinks. Towns are also grouped by the State to which they belong. All States are linked into a ring, so that they can be sequentially accessed.

ZZ_HYPER_SINGLE_GRAPH(hwy,Town,Highway);
ZZ_HYPER_SINGLE_GRAPH(air,Town,AirLine);
ZZ_HYPER_SINGLE_AGGREGATE(byState,State,Town);
ZZ_HYPER_SINGLE_COLLECT(sRing,Header,State);

Here hwy represents a graph, which has towns as nodes and Highways as edges, and air represents a graph which has Towns as nodes and Airlinks as edges. sRing is a ring of all States, and byState is one level of hierarchy, which groups Towns that belong to one State. We added one object, called Header, to encapsulate the entry into the rings of states, sRing.

Example 2:

In electrical netlists, such as those used on VLSI chips or printed circuit boards. we deal with Blocks that have Pins. Pins are connected by signal Nets. Both Blocks and Nets must be sequentially accessible, each separately.

ZZ_HYPER_SINGLE_COLLECT(bRing,Header,Block);
ZZ_HYPER_SINGLE_COLLECT(nRing,Header,Net);
ZZ_HYPER_SINGLE_AGGREGATE(byBlock,Block,Pin);
ZZ_HYPER_SINGLE_AGGREGATE(byNet,Net,Pin);

Here, byBlock allows access to all the pins of a given block, and byNet allows access to all the pins on a Net. Header is a dummy object to encapsulate entry points of the two rings.

Example 3:

When dealing with a list of Employee records, we may want to keep a hash table for fast access of the records by name. At the same time, we want to record the time when the objects were created or modified. A hash table must exist as an attribute of some object; object Header keeps both the hash table and the entry point for the ring:

class Header {
    ZZ_EXT_Header
};
class Employee {
    ZZ_EXT_Employee
        ...
};
ZZ_HYPER_SINGLE_COLLECT(eRing,Header,Employee);<%0>
ZZ_HYPER_TIME_STAMP(Employee); 
ZZ_HYPER_HASH(eTable,Header,Employee); 

Here, eRing is the ring of all employees. Each record will have a time stamp. Hash table eTable will allow fast access to the Employee records.

IMPORTANT:

Under UNIX, the organization id must not start with a blank character. For example:

ZZ_HYPER_SINGLE_GRAPH( myGraph,Town,Hwy);

results in a compiler error.

8.5 ZZ_LOCAL

In the previous two sections, it was explained that in C, you declare data organizations with ZZ_ORG_..(), while in C++ you declare them with ZZ_HYPER_..(). Both these declarations have a global character, and even though the interface classes that represent the organization do not contain any data, some users expressed a wish to encapsulate these organizations under some class, to make them completely invisible from the outside.

For this purpose, the class must be designed with some minor differences in how it is constructed, which is exactly what ZZ_LOCAL_..() is. ZZ_LOCAL_..() has one more parameter than the corresponding ZZ_ORG_..or ZZ_HYPER_.. statements do. The last parameter specifies the class under which the declaration will be hidden (see the example below). The ZZ_LOCAL_..() statement must be in the beginning of the class, before ZZ_EXT_..

The following example shows how a RING can be neatly encapsulated inside a class. This is a much cleaner implementation than you can achieve with standard class libraries. Note that ZZ_HYPER_UTILITIES() is always global; functions such as memory management and persistency are difficult if not impossible to make local.

// Testing DOL an organization with a local scope
// Here RING myRing is knows only within the scope of class A
#include <<stdio.h>>
#define ZZmain
#include zzincl.h
class A {
    static A *entry;
    int a;
    ZZ_EXT_A
public:
    int val(void){return a;};
    A(){entry=NULL;}
    A(int i);
    A* next(void);
    ZZ_LOCAL_SINGLE_RING(myRing,A,A);
};
ZZ_HYPER_UTILITIES(util);
A* A::next(void){return myRing.fwd(this);}
A::A(int i){a=i; entry=myRing.add(entry,this);}
int main(void){
A aa,*a1,*a2;
a1=new A(1);
a2=new A(2);
a2=a1-.next();
printf("%d %d\n", a1-.val(),a2-.val());
// a1=myRing.fwd(a2); causes compiler error, unknown myRing }
#include "zzfunc.c"

This program is included in the test suite as test36b.c.

WARNING:

!!TODO - Check this with Jiri. This style of localizing data organization does not agree well with the basic concept of this entire library, and is not recommended for use. In spite of our effort, some organizations (for example HASH) do not work with ZZ_LOCAL. Instead of using ZZ_LOCAL, we recommend the following strategy, where the organization remains global, but the object that holds its actual implementation is private. All access to the hash table is encapsulated under class Dict. This style naturally fits the design of the library, and the resulting code is clean and simple. (This example was designed in cooperation with Jay Weininger from Reuters.)

Here Dict keeps a hash table of variable length tokens, with only one copy for each string.

#include <iostream.h>
#define ZZmain
#include "zzincl.h"
// holder of the hash table|
class Table {
    ZZ_EXT_Table
};
// represents one name entry
class Token {
    ZZ_EXT_Token
public:
    Token(){}
    Token(char *n);
    ~Token();
};
// encapsulates the hash table
// Note the absence of ZZ_EXT_Dict
class Dict { 
    Table *tab; // hides the hash table
public:
    Dict(int sz);
    ~Dict();
    void addName(char *n);
    char *getName(char *n);
    void delName(char *n);
};
ZZ_HYPER_HASH(hash,Table,Token);
ZZ_HYPER_NAME(name,Token);
ZZ_HYPER_UTILITIES(util);
int hash_class::cmp(Token *b1,Token *b2){
    return strcmp(name.fwd(b1),name.fwd(b2)); }
int hash_class::hash(Token *b,int size){
    int ZZhashStr(char *,int);
    return ZZhashStr(name.fwd(b),size); 
}
Dict::Dict(int sz){tab=new Table; hash.form(tab,sz);} 
Dict::~Dict(){hash.free(tab);} 
Token::Token(char *n){
    char* p=util.strAlloc(n); name.add(this,p);}
Token::~Token(){
    char* p=name.del(this); if(p)util.strFree(p);} 
void Dict::addName(char *n){
    Token* t=new Token(n);
    Token* p=hash.get(tab,t);
    if(!p)hash.add(tab,t); else delete t;
}
char* Dict::getName(char *n){
    static Token t;
    char *p;
    name.add(&t,n);
    Token* tp=hash.get(tab,&t);
    if(tp)p=name.fwd(tp); else p=NULL;
    (void)name.del(&t);
    return p;
}
void Dict::delName(char *n){
    static Token t;
    name.add(&t,n);
    Token* tp=hash.get(tab,&t);
    if(tp)(void)hash.del(tab,tp);
    (void)name.del(&t);
}
void main(void){
    Dict a(100); char *n;
    a.addName("brown"); 
    a.addName("black"); 
    a.addName("brown"); 
    a.addName("brown"); 
    a.delName("brown");
    n=a.getName("black"); if(n)cout<<n<<"\n";
    n=a.getName("brown"); if(n)cout<<n<<"\n";
};
#include "zzfunc.c"
// The program prints only one line: black

This example is part of the test suite, see test36c.c.

8.6 SYNTAX OF OTHER ZZ CALLS

When using DOL, most of the time, you will need only two additional macros with the prefix ZZ: ZZ_INITIAL() and ZZ_CHECK(). These macros must be inserted into the constructor/destructor for your objects that have ZZ_EXT statements. ZZ_INITIAL() automatically initializes all internal pointers as disconnected; ZZ_CHECK() checks that the object is disconnected from all organizations before it is deallocated.

All other ZZ calls, whether macros or functions, are encapsulated in the hyper-objects and you, as their user, do not have to know about them.

Note that in C++ the C macros for traversing sets, ZZ_A_TRAVERSE(){ ... }ZZ_A_END are replaced by iterators, which are automatically created by the ZZ_HYPER_.. statements. These iterators do not have the ZZ_ prefix, and behave like normal C++ iterators. For most of the iterators in DOL, the overloaded ++ operator is used to get to the next object in the set.

8.7 VERSION OF THIS LIBRARY

A special global string ZZorgcVersion, which appears at the top of the zzincl.h file, contains information about the current version of the library. This information allows you to recover the version number for old executables. Use the "strings" program, and search for text "Version".

8.8 CMP() AND OTHER AUXILLIARY FUNCTIONS

Both the documentation and the test programs frequently use auxilliary free floating functions, such as the compare function needed for sorting (equivalent to the cmp() function needed for qsort). Similar functions are needed when you want to sort any of the organizations in this library, or when you want to use the binary heap or the hash table.

A much better coding style is not to use free floating functions, but to make these functions static and assigned to the class of objects that are being compared. Both styles are supported by this library. For examples of the better style - see the example in Chap.7, or test0r.c.

The advantages of not using virtual cmp() functions as it is common in some other class libraries are that:

Note that the cmp() and hash() functions for hash tables are assigned to the organization itself (see Chap.14.5), and therefore must be coded as:

inline int _class::cmp(oType*,oType*);

inline int _class::hash(oType*,int);

 

Chapter 7: UML Chapter 9: Single User Mode