SYMTAB -- General package for managing symbol tables.  The logical view of a 
symbol table is a collection of symbol structures.  The contents of a symbol
description structure are user defined, but the size of the structure is fixed.
The symbol name is a character string of arbitrary size, all characters of
which are significant.  The storage semantics of the symbol table are those
of a lifo stack, i.e., those symbols most recently defined must be the first
deleted.  There is no fixed limit on the size of a symbol table; additional
space will be dynamically allocated at run time if necessary.


       stp =     stopen (name, len_index, len_stab, sz_sbuf)
                stclose (stp)
                 stmark (stp, marker)           # mark storage
                 stfree (stp, marker)           # free to marked state
      nsym = stnsymbols (stp, marker)		# number of symbols in table
                 stinfo (stp, outfd)            # print info about symbol table

       sym =    stenter (stp, key, symlen)      # enter new symbol in table
       sym =     stfind (stp, key)              # search table for symbol
      nsym =  stfindall (stp, key, sym, maxsym) # find all occurrences of symbol
       sym =     sthead (stp)                   # last symbol entered into table
       sym =     stnext (stp, sym)              # next symbol on global list
     charp =     stname (stp, sym)              # access key name string

    offset =     stpstr (stp, str, minchars)    # put string in string buffer
    offset =    stalloc (stp, nints)            # alloc space in STAB
     charp =  strefsbuf (stp, offset)           # convert sbuf offset into charp
      intp =  strefstab (stp, offset)           # convert stab offset into intp

                 stsave (stp, fd)               # save symbol table in a file
       stp =  strestore (fd)                    # restore symbol table from file
              stsqueeze (stp)                   # return unused storage
     chars =     stsize (stp)			# chars req'd to store table


The symbol table is maintained as a multi-threaded linked list.  This provides
the efficiency of a hash table plus stack like semantics for redefinitions and
for freeing blocks of variables.  There are three primary data structures
internally, an array of pointers to the heads of the threads (the index),
a buffer containing the list elements (the symbol table), and a string buffer.
These data structures are dynamically allocated and will be automatically
reallocated at runtime if overflow occurs.  The number of threads is fixed at
table open time and determines the efficiency of table lookup.  The expected
running time is O(1) for well conditioned tables, i.e., tables with a sparse
index.  The worst case running time is O(N), i.e., the same as a linear search,
but of course the worst case is very unlikely to occur.  Symbol entry and
storage reclamation are especially efficient due to the use of a stack rather
than a heap for symbol storage.

	symtab descriptor
		index (integer array, size fixed at open time)
		sbuf  (char array, dynamic)
		stab  (list of symstructs, dynamic)

Each symbol consists of a variable size structure (symstruct) in STAB containing
references to one or more associated strings in SBUF.  A symstruct consists of
a fixed size SYMTAB header followed by a user defined structure, the size of
which is fixed at STENTER time.  Each symstruct is linked on two lists,
a global list and a hash list.  All symstructs are linked on the global list
which is ordered with the most recently entered symbol at the head of the list.
For a well conditioned table each hash list will typically contain zero or one
symbols, more when there are redefinitions or when identifiers happen to hash
to the same thread.  Often it is useful to store different types of entries
in the same symbol table; since symstructs may vary in size this may be done
efficiently.

The STOPEN procedure is used to create a new symbol table.  The 'name' argument
may be any string and is used for documentation purposes only.  The index
length argument sets the size of the hash index which is fixed for the
lifetime of the table.  The remaining two arguments specify the amount of
symbol table space and string storage space to be initially allocated for the
table.  The actual table may grow larger at runtime, but reallocation can be
expensive hence it is desirable to preallocate a large space if it is known
that the table will be large.

The STMARK and STFREE procedures mark and free storage on the symbol table
stack.  All symbols defined or redefined after a call to STMARK will be deleted
and storage freed by a call to STFREE.  If a redef is freed the next most
recent definition becomes current.  STFREE returns as its function value the
number of redefined variables uncovered by the free operation.  The calling
program must mark and free in the correct order or the symbol table may be
trashed.  The argument to STMARK is a magic integer which currently references
a marker record in the STAB containing the information necessary to mark and
free storage (an integer is not large enough to hold all of the necessary
information).

STENTER is used to enter a new symbol into the symbol table.  If the named
symbol is already present the new entry will nondestructively supercede the
old, which may be recovered by a subsequent STFREE (this is especially useful
for defining local contexts, e.g., when parsing a block structured language).
STENTER returns a pointer to the newly allocated symbol structure.  This pointer
may be invalidated if the symbol table buffer has to be reallocated in a
subsequent call to STENTER.  STFIND searches the symbol table for the named
key (symbol), returning a pointer to the most recently defined entry or NULL.
Once again, this pointer may be invalidated by a subsquent call which adds
to or removes symbols from the table.  STFINDALL finds all occurrences (or
some maximum number of occurrences), returning an array of symstruct pointers
ordered with the most recently defined symbols at the left.  STHEAD and STNEXT
are used to scan symbols in the reverse of the order in which they were
entered, e.g., for unkeyed symbol table searches.

The symbol table uses an associated string buffer for string storage.  Keys
(symbol names) are automatically added to the string buffer by STENTER.
The string buffer is maintained as a stack, with STMARK and STFREE marking
and freeing storage in the string buffer as well as in the symbol table buffer.
The user may store data in either the string buffer or the symbol table
provided the stack semantics are observed when marking and freeing storage.
To permit dynamic reallocation, storage is referenced by offset rather than
by pointer.  Only offsets into SBUF or STAB should be stored in symbol table
entries.  String storage is allocated in SBUF with STPSTR (which also deposits
the string).  Integer aligned storage is allocated in STAB with STALLOC.
The offset of a string stored in SBUF may be converted to a char pointer
with STREFSBUF.  The offset of a storage area in STAB may be converted into
an integer pointer with STREFSTAB.  Pointer conversions such as these should
not be done in the calling program since doing so requires knowledge of the
internal SYMTAB data structures (in particular, that symbol table storage
is contiguous).

A symbol table may be saved in a binary file using STSAVE.  The space which
will be required to store the symbol table may be queried in advance with
STSIZE.  The internal SYMTAB data structures are simply appended to the file.
The symbol table itself is not affected.  A saved symbol table may be restored
to memory with STRESTORE, which is used in place of STOPEN.  The file must be
opened and positioned to the correct offset before STRESTORE is called.
It may be desirable to call STSQUEEZE before calling STSAVE or STSIZE,
to minimize the file storage required to store the symbol table.
