[med-svn] [kmer-tools] 04/13: Imported Upstream version 0~20150903+r2013

Afif Elghraoui afif-guest at moszumanska.debian.org
Sat Jan 2 07:24:11 UTC 2016


This is an automated email from the git hooks/post-receive script.

afif-guest pushed a commit to branch master
in repository kmer-tools.

commit f2f4f9570561175ea5af577ff239cdc99ee87786
Author: Afif Elghraoui <afif at ghraoui.name>
Date:   Fri Jan 1 20:06:05 2016 -0800

    Imported Upstream version 0~20150903+r2013
---
 PACKAGING                                |  104 +
 configure.sh                             |    6 +-
 libutil/kazlib/Make.include              |   27 +
 libutil/kazlib/blast.pl                  |   33 +
 libutil/kazlib/dict.c                    | 1238 +++++++++
 libutil/kazlib/dict.h                    |  142 +
 libutil/kazlib/docs/CHANGES              |  290 +++
 libutil/kazlib/docs/MUST_READ            |   25 +
 libutil/kazlib/docs/README               |   66 +
 libutil/kazlib/docs/docs.ist             |    4 +
 libutil/kazlib/docs/docs.ltx             | 4155 ++++++++++++++++++++++++++++++
 libutil/kazlib/drivers/dict-main.c       |  300 +++
 libutil/kazlib/drivers/except-main.c     |   57 +
 libutil/kazlib/drivers/hash-main.c       |  187 ++
 libutil/kazlib/drivers/list-main.c       |  152 ++
 libutil/kazlib/drivers/sfx-main.c        |   41 +
 libutil/kazlib/except.c                  |  347 +++
 libutil/kazlib/except.h                  |  147 ++
 libutil/kazlib/hash.c                    |  837 ++++++
 libutil/kazlib/hash.h                    |  238 ++
 libutil/kazlib/list.c                    |  766 ++++++
 libutil/kazlib/list.h                    |  152 ++
 libutil/kazlib/sfx.c                     | 1138 ++++++++
 libutil/kazlib/sfx.h                     |   46 +
 meryl/args.C                             |    2 +-
 meryl/build.C                            |   70 +-
 meryl/estimate.C                         |  136 +-
 meryl/kmer-mask.C                        |  344 ++-
 meryl/mapMers.C                          |   26 +-
 meryl/meryl.H                            |    7 +-
 sim4dbutils/reportAlignmentDifferences.C |    2 +-
 31 files changed, 10810 insertions(+), 275 deletions(-)

diff --git a/PACKAGING b/PACKAGING
new file mode 100644
index 0000000..dde039c
--- /dev/null
+++ b/PACKAGING
@@ -0,0 +1,104 @@
+
+ALL
+---
+
+rm -rf .svn
+
+
+
+ATAC
+----
+
+rm -rf ESTmapper
+rm -rf ESTmapper\ GSAC.pdf
+rm -rf ESTmapper\ GSAC.ppt
+rm -rf ESTmapper\ LaTeX
+rm -rf Makefile.wiki
+rm -rf PACKAGING
+rm -rf README.leaff
+rm -rf README.meryl
+rm -rf README.sim4db
+rm -rf developer-doc
+rm -rf libsim4
+rm -rf seagen
+rm -rf sim4db
+rm -rf sim4dbutils
+rm -rf snapper
+rm -rf tapper
+rm -rf trie
+
+
+MERYL
+-----
+
+rm -rf ESTmapper
+rm -rf ESTmapper\ GSAC.pdf
+rm -rf ESTmapper\ GSAC.ppt
+rm -rf ESTmapper\ LaTeX
+rm -rf Makefile.wiki
+rm -rf PACKAGING
+rm -rf README.atac
+rm -rf README.leaff
+rm -rf README.sim4db
+rm -rf atac-driver
+rm -rf developer-doc
+rm -rf leaff
+rm -rf libsim4
+rm -rf seagen
+rm -rf seatac
+rm -rf sim4db
+rm -rf sim4dbutils
+rm -rf snapper
+rm -rf tapper
+rm -rf trie
+
+
+SIM4DB
+------
+
+rm -rf ESTmapper
+rm -rf ESTmapper\ GSAC.pdf
+rm -rf ESTmapper\ GSAC.ppt
+rm -rf ESTmapper\ LaTeX
+rm -rf Makefile.wiki
+rm -rf PACKAGING
+rm -rf README.atac
+rm -rf README.meryl
+rm -rf atac-driver
+rm -rf developer-doc
+rm -rf libkmer
+rm -rf libmeryl
+rm -rf meryl
+rm -rf seagen
+rm -rf seatac
+rm -rf snapper
+rm -rf tapper
+rm -rf trie
+
+
+ESTmapper
+---------
+
+rm -rf ESTmapper\ GSAC.pdf
+rm -rf ESTmapper\ GSAC.ppt
+rm -rf ESTmapper\ LaTeX
+rm -rf Makefile.wiki
+rm -rf PACKAGING
+rm -rf README.atac
+rm -rf README.leaff
+rm -rf README.meryl
+rm -rf README.sim4db
+rm -rf atac-driver
+rm -rf developer-doc
+rm -rf seatac
+rm -rf snapper
+rm -rf tapper
+rm -rf trie
+
+
+rm -rf ATAC-r2008/.svn ESTmapper-r2008/.svn meryl-r2008/.svn sim4db-r2008/.svn
+
+tar -cf ATAC-r2008.tar      ATAC-r2008
+tar -cf ESTmapper-r2008.tar ESTmapper-r2008
+tar -cf meryl-r2008.tar     meryl-r2008
+tar -cf sim4db-r2008.tar    sim4db-r2008
diff --git a/configure.sh b/configure.sh
index 95a0bf0..f1a12a4 100755
--- a/configure.sh
+++ b/configure.sh
@@ -360,7 +360,7 @@ esac
 
 
 cat <<EOF >> Make.compilers
-PERL              := /usr/bin/perl
+PERL              := /usr/bin/env perl
 .EXE              := 
 .SO               := .so
 .A                := .a
@@ -369,8 +369,8 @@ CLD               := \${CC}
 CXXLD             := \${CXX}
 CCDEP             := \${CC} -MM -MG
 CXXDEP	          := \${CXX} -MM -MG
-CLIBS             += -lm -lbz2
-CXXLIBS           += -lm -lbz2
+CLIBS             += -lm
+CXXLIBS           += -lm
 PYTHON            := $PYTHON
 PYTHON_H          := $CFLAGS_PYTHON/Python.h
 CFLAGS_PYTHON     := -I$CFLAGS_PYTHON
diff --git a/libutil/kazlib/Make.include b/libutil/kazlib/Make.include
new file mode 100644
index 0000000..7ecb369
--- /dev/null
+++ b/libutil/kazlib/Make.include
@@ -0,0 +1,27 @@
+# -*- makefile -*-
+
+src    := $/dict.c \
+          $/dict.h \
+          $/except.c \
+          $/except.h \
+          $/hash.c \
+          $/hash.h \
+          $/list.c \
+          $/list.h \
+          $/sfx.c \
+          $/sfx.h
+
+tst    := $/dict-main.c \
+          $/except-main.c \
+          $/hash-main.c \
+          $/list-main.c \
+          $/sfx-main.c
+
+$/.C_SRCS    :=$(filter %.c,${src})
+$/.CXX_SRCS  :=$(filter %.C,${src})
+$/.CXX_LIBS  :=$/libkaz.a
+
+$/.CLEAN := $/*.o
+
+$/libkaz.a: ${$/.C_SRCS:.c=.o} ${$/.CXX_SRCS:.C=.o}
+
diff --git a/libutil/kazlib/blast.pl b/libutil/kazlib/blast.pl
new file mode 100755
index 0000000..63351c9
--- /dev/null
+++ b/libutil/kazlib/blast.pl
@@ -0,0 +1,33 @@
+#!/usr/bin/perl
+
+#
+# This is a program whose output can be piped to the test drivers for
+# hash.c and dict.c. It inserts a bunch of data and then deletes it all.
+#
+# The $modulus should be a prime number. This ensures that the $modulus - 1
+# generated keys are all distinct.  The $factor_i and $factor_d values need not
+# be prime, but it should not be a multiple of $modulus (including zero),
+# otherwise a sequence of duplicate keys will be generated: choose numbers
+# in the range [1, $modulus - 1]. Choosing 1 means that
+# insertions (or deletions) will take place in order.
+# The purpose of using the prime modulus number is to generate a repeatable
+# sequence of unique keys that is (possibly) not in sorted order.
+#
+
+# $modulus = 200003;
+# $factor_i = 100;
+# $factor_d = 301;
+
+$modulus = 6113;
+$factor_i = 1669;
+$factor_d = 2036;
+
+for ($i = 1; $i < $modulus; $i++) {
+    printf("a %d %d\n", ($i * $factor_i) % $modulus, $i);
+}
+
+for ($i = 1; $i < $modulus; $i++) {
+    printf("d %d\n", ($i * $factor_d) % $modulus);
+}
+
+print "t\nq\n"
diff --git a/libutil/kazlib/dict.c b/libutil/kazlib/dict.c
new file mode 100644
index 0000000..cd98498
--- /dev/null
+++ b/libutil/kazlib/dict.c
@@ -0,0 +1,1238 @@
+/*
+ * Dictionary Abstract Data Type
+ * Copyright (C) 1997 Kaz Kylheku <kaz at ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ */
+
+#define NDEBUG
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <assert.h>
+#define DICT_IMPLEMENTATION
+#include "dict.h"
+
+//  bpw 20050309 define this to use a qsort(3) compatible sort function,
+//  requiring two dereferences to get the data instead of one.
+//
+#define BE_QSORT_COMPATIBLE
+
+/*
+ * These macros provide short convenient names for structure members,
+ * which are embellished with dict_ prefixes so that they are
+ * properly confined to the documented namespace. It's legal for a 
+ * program which uses dict to define, for instance, a macro called ``parent''.
+ * Such a macro would interfere with the dnode_t struct definition.
+ * In general, highly portable and reusable C modules which expose their
+ * structures need to confine structure member names to well-defined spaces.
+ * The resulting identifiers aren't necessarily convenient to use, nor
+ * readable, in the implementation, however!
+ */
+
+#define left dict_left
+#define right dict_right
+#define parent dict_parent
+#define color dict_color
+#define key dict_key
+#define data dict_data
+
+#define nilnode dict_nilnode
+#define nodecount dict_nodecount
+#define maxcount dict_maxcount
+#define compare dict_compare
+#define allocnode dict_allocnode
+#define freenode dict_freenode
+#define context dict_context
+#define dupes dict_dupes
+
+#define dictptr dict_dictptr
+
+#define dict_root(D) ((D)->nilnode.left)
+#define dict_nil(D) (&(D)->nilnode)
+#define DICT_DEPTH_MAX 64
+
+static dnode_t *dnode_alloc(void *context);
+static void dnode_free(dnode_t *node, void *context);
+
+/*
+ * Perform a ``left rotation'' adjustment on the tree.  The given node P and
+ * its right child C are rearranged so that the P instead becomes the left
+ * child of C.   The left subtree of C is inherited as the new right subtree
+ * for P.  The ordering of the keys within the tree is thus preserved.
+ */
+
+static void rotate_left(dnode_t *upper)
+{
+    dnode_t *lower, *lowleft, *upparent;
+
+    lower = upper->right;
+    upper->right = lowleft = lower->left;
+    lowleft->parent = upper;
+
+    lower->parent = upparent = upper->parent;
+
+    /* don't need to check for root node here because root->parent is
+       the sentinel nil node, and root->parent->left points back to root */
+
+    if (upper == upparent->left) {
+	upparent->left = lower;
+    } else {
+	assert (upper == upparent->right);
+	upparent->right = lower;
+    }
+
+    lower->left = upper;
+    upper->parent = lower;
+}
+
+/*
+ * This operation is the ``mirror'' image of rotate_left. It is
+ * the same procedure, but with left and right interchanged.
+ */
+
+static void rotate_right(dnode_t *upper)
+{
+    dnode_t *lower, *lowright, *upparent;
+
+    lower = upper->left;
+    upper->left = lowright = lower->right;
+    lowright->parent = upper;
+
+    lower->parent = upparent = upper->parent;
+
+    if (upper == upparent->right) {
+	upparent->right = lower;
+    } else {
+	assert (upper == upparent->left);
+	upparent->left = lower;
+    }
+
+    lower->right = upper;
+    upper->parent = lower;
+}
+
+/*
+ * Do a postorder traversal of the tree rooted at the specified
+ * node and free everything under it.  Used by dict_free().
+ */
+
+static void free_nodes(dict_t *dict, dnode_t *node, dnode_t *nil)
+{
+    if (node == nil)
+	return;
+    free_nodes(dict, node->left, nil);
+    free_nodes(dict, node->right, nil);
+    dict->freenode(node, dict->context);
+}
+
+/*
+ * This procedure performs a verification that the given subtree is a binary
+ * search tree. It performs an inorder traversal of the tree using the
+ * dict_next() successor function, verifying that the key of each node is
+ * strictly lower than that of its successor, if duplicates are not allowed,
+ * or lower or equal if duplicates are allowed.  This function is used for
+ * debugging purposes. 
+ */
+
+static int verify_bintree(dict_t *dict)
+{
+    dnode_t *first, *next;
+
+    first = dict_first(dict);
+
+    if (dict->dupes) {
+	while (first && (next = dict_next(dict, first))) {
+#ifdef BE_QSORT_COMPATIBLE
+	    if (dict->compare(&first->key, &next->key) > 0)
+		return 0;
+#else
+	    if (dict->compare(first->key, next->key) > 0)
+		return 0;
+#endif
+	    first = next;
+	}
+    } else {
+	while (first && (next = dict_next(dict, first))) {
+#ifdef BE_QSORT_COMPATIBLE
+            if (dict->compare(&first->key, &next->key) >= 0)
+		return 0;
+#else
+	    if (dict->compare(first->key, next->key) >= 0)
+		return 0;
+#endif
+	    first = next;
+	}
+    }
+    return 1;
+}
+
+
+/*
+ * This function recursively verifies that the given binary subtree satisfies
+ * three of the red black properties. It checks that every red node has only
+ * black children. It makes sure that each node is either red or black. And it
+ * checks that every path has the same count of black nodes from root to leaf.
+ * It returns the blackheight of the given subtree; this allows blackheights to
+ * be computed recursively and compared for left and right siblings for
+ * mismatches. It does not check for every nil node being black, because there
+ * is only one sentinel nil node. The return value of this function is the
+ * black height of the subtree rooted at the node ``root'', or zero if the
+ * subtree is not red-black.
+ */
+
+static unsigned int verify_redblack(dnode_t *nil, dnode_t *root)
+{
+    unsigned height_left, height_right;
+
+    if (root != nil) {
+	height_left = verify_redblack(nil, root->left);
+	height_right = verify_redblack(nil, root->right);
+	if (height_left == 0 || height_right == 0)
+	    return 0;
+	if (height_left != height_right)
+	    return 0;
+	if (root->color == dnode_red) {
+	    if (root->left->color != dnode_black)
+		return 0;
+	    if (root->right->color != dnode_black)
+		return 0;
+	    return height_left;
+	}
+	if (root->color != dnode_black)
+	    return 0;
+	return height_left + 1;
+    } 
+    return 1;
+}
+
+/*
+ * Compute the actual count of nodes by traversing the tree and
+ * return it. This could be compared against the stored count to
+ * detect a mismatch.
+ */
+
+static dictcount_t verify_node_count(dnode_t *nil, dnode_t *root)
+{
+    if (root == nil)
+	return 0;
+    else
+	return 1 + verify_node_count(nil, root->left)
+	    + verify_node_count(nil, root->right);
+}
+
+/*
+ * Verify that the tree contains the given node. This is done by
+ * traversing all of the nodes and comparing their pointers to the
+ * given pointer. Returns 1 if the node is found, otherwise
+ * returns zero. It is intended for debugging purposes.
+ */
+
+static int verify_dict_has_node(dnode_t *nil, dnode_t *root, dnode_t *node)
+{
+    if (root != nil) {
+	return root == node
+		|| verify_dict_has_node(nil, root->left, node)
+		|| verify_dict_has_node(nil, root->right, node);
+    }
+    return 0;
+}
+
+
+/*
+ * Dynamically allocate and initialize a dictionary object.
+ */
+
+dict_t *dict_create(dictcount_t maxcount, dict_comp_t comp)
+{
+    dict_t *new = malloc(sizeof *new);
+
+    if (new) {
+	new->compare = comp;
+	new->allocnode = dnode_alloc;
+	new->freenode = dnode_free;
+	new->context = NULL;
+	new->nodecount = 0;
+	new->maxcount = maxcount;
+	new->nilnode.left = &new->nilnode;
+	new->nilnode.right = &new->nilnode;
+	new->nilnode.parent = &new->nilnode;
+	new->nilnode.color = dnode_black;
+	new->dupes = 0;
+    }
+    return new;
+}
+
+/*
+ * Select a different set of node allocator routines.
+ */
+
+void dict_set_allocator(dict_t *dict, dnode_alloc_t al,
+	dnode_free_t fr, void *context)
+{
+    assert (dict_count(dict) == 0);
+    assert ((al == NULL && fr == NULL) || (al != NULL && fr != NULL));
+
+    dict->allocnode = al ? al : dnode_alloc;
+    dict->freenode = fr ? fr : dnode_free;
+    dict->context = context;
+}
+
+/*
+ * Free a dynamically allocated dictionary object. Removing the nodes
+ * from the tree before deleting it is required.
+ */
+
+void dict_destroy(dict_t *dict)
+{
+    assert (dict_isempty(dict));
+    free(dict);
+}
+
+/*
+ * Free all the nodes in the dictionary by using the dictionary's
+ * installed free routine. The dictionary is emptied.
+ */
+
+void dict_free_nodes(dict_t *dict)
+{
+    dnode_t *nil = dict_nil(dict), *root = dict_root(dict);
+    free_nodes(dict, root, nil);
+    dict->nodecount = 0;
+    dict->nilnode.left = &dict->nilnode;
+    dict->nilnode.right = &dict->nilnode;
+}
+
+/*
+ * Obsolescent function, equivalent to dict_free_nodes
+ */
+
+void dict_free(dict_t *dict)
+{
+#ifdef KAZLIB_OBSOLESCENT_DEBUG
+    assert ("call to obsolescent function dict_free()" && 0);
+#endif
+    dict_free_nodes(dict);
+}
+
+/*
+ * Initialize a user-supplied dictionary object.
+ */
+
+dict_t *dict_init(dict_t *dict, dictcount_t maxcount, dict_comp_t comp)
+{
+    dict->compare = comp;
+    dict->allocnode = dnode_alloc;
+    dict->freenode = dnode_free;
+    dict->context = NULL;
+    dict->nodecount = 0;
+    dict->maxcount = maxcount;
+    dict->nilnode.left = &dict->nilnode;
+    dict->nilnode.right = &dict->nilnode;
+    dict->nilnode.parent = &dict->nilnode;
+    dict->nilnode.color = dnode_black;
+    dict->dupes = 0;
+    return dict;
+}
+
+/* 
+ * Initialize a dictionary in the likeness of another dictionary
+ */
+
+void dict_init_like(dict_t *dict, const dict_t *template)
+{
+    dict->compare = template->compare;
+    dict->allocnode = template->allocnode;
+    dict->freenode = template->freenode;
+    dict->context = template->context;
+    dict->nodecount = 0;
+    dict->maxcount = template->maxcount;
+    dict->nilnode.left = &dict->nilnode;
+    dict->nilnode.right = &dict->nilnode;
+    dict->nilnode.parent = &dict->nilnode;
+    dict->nilnode.color = dnode_black;
+    dict->dupes = template->dupes;
+
+    assert (dict_similar(dict, template));
+}
+
+/*
+ * Remove all nodes from the dictionary (without freeing them in any way).
+ */
+
+static void dict_clear(dict_t *dict)
+{
+    dict->nodecount = 0;
+    dict->nilnode.left = &dict->nilnode;
+    dict->nilnode.right = &dict->nilnode;
+    dict->nilnode.parent = &dict->nilnode;
+    assert (dict->nilnode.color == dnode_black);
+}
+
+
+/*
+ * Verify the integrity of the dictionary structure.  This is provided for
+ * debugging purposes, and should be placed in assert statements.   Just because
+ * this function succeeds doesn't mean that the tree is not corrupt. Certain
+ * corruptions in the tree may simply cause undefined behavior.
+ */ 
+
+int dict_verify(dict_t *dict)
+{
+    dnode_t *nil = dict_nil(dict), *root = dict_root(dict);
+
+    /* check that the sentinel node and root node are black */
+    if (root->color != dnode_black)
+      return(0 * fprintf(stderr, "dict_verify()-- Root node not black!\n"));
+    if (nil->color != dnode_black)
+      return(0 * fprintf(stderr, "dict_verify()-- Nil node not black!\n"));
+    if (nil->right != nil)
+      return(0 * fprintf(stderr, "dict_verify()-- Nul->right not Nil!\n"));
+    /* nil->left is the root node; check that its parent pointer is nil */
+    if (nil->left->parent != nil)
+      return(0 * fprintf(stderr, "dict_verify()-- Nul->left->parent is not Nil!\n"));
+    /* perform a weak test that the tree is a binary search tree */
+    if (!verify_bintree(dict))
+      return(0 * fprintf(stderr, "dict_verify()-- Not a binary search tree!\n"));
+    /* verify that the tree is a red-black tree */
+    if (!verify_redblack(nil, root))
+      return(0 * fprintf(stderr, "dict_verify()-- Not a red-black tree!\n"));
+    if (verify_node_count(nil, root) != dict_count(dict))
+      return(0 * fprintf(stderr, "dict_verify()-- Node count is wrong!\n"));
+    return 1;
+}
+
+/*
+ * Determine whether two dictionaries are similar: have the same comparison and
+ * allocator functions, and same status as to whether duplicates are allowed.
+ */
+
+int dict_similar(const dict_t *left, const dict_t *right)
+{
+    if (left->compare != right->compare)
+	return 0;
+
+    if (left->allocnode != right->allocnode)
+	return 0;
+
+    if (left->freenode != right->freenode)
+	return 0;
+
+    if (left->context != right->context)
+	return 0;
+
+    if (left->dupes != right->dupes)
+	return 0;
+
+    return 1;
+}
+
+/*
+ * Locate a node in the dictionary having the given key.
+ * If the node is not found, a null a pointer is returned (rather than 
+ * a pointer that dictionary's nil sentinel node), otherwise a pointer to the
+ * located node is returned.
+ */
+
+dnode_t *dict_lookup(dict_t *dict, const void *key)
+{
+    dnode_t *root = dict_root(dict);
+    dnode_t *nil = dict_nil(dict);
+    dnode_t *saved;
+    int result;
+
+    /* simple binary search adapted for trees that contain duplicate keys */
+
+    while (root != nil) {
+#ifdef BE_QSORT_COMPATIBLE
+	result = dict->compare(&key, &root->key);
+#else
+	result = dict->compare(key, root->key);
+#endif
+	if (result < 0)
+	    root = root->left;
+	else if (result > 0)
+	    root = root->right;
+	else {
+	    if (!dict->dupes) {	/* no duplicates, return match		*/
+		return root;
+	    } else {		/* could be dupes, find leftmost one	*/
+		do {
+		    saved = root;
+		    root = root->left;
+#ifdef BE_QSORT_COMPATIBLE
+		    while (root != nil && dict->compare(&key, &root->key))
+			root = root->right;
+#else
+		    while (root != nil && dict->compare(key, root->key))
+			root = root->right;
+#endif
+		} while (root != nil);
+		return saved;
+	    }
+	}
+    }
+
+    return NULL;
+}
+
+/*
+ * Look for the node corresponding to the lowest key that is equal to or
+ * greater than the given key.  If there is no such node, return null.
+ */
+
+dnode_t *dict_lower_bound(dict_t *dict, const void *key)
+{
+    dnode_t *root = dict_root(dict);
+    dnode_t *nil = dict_nil(dict);
+    dnode_t *tentative = 0;
+
+    while (root != nil) {
+#ifdef BE_QSORT_COMPATIBLE
+	int result = dict->compare(&key, &root->key);
+#else
+	int result = dict->compare(key, root->key);
+#endif
+
+	if (result > 0) {
+	    root = root->right;
+	} else if (result < 0) {
+	    tentative = root;
+	    root = root->left;
+	} else {
+	    if (!dict->dupes) {
+	    	return root;
+	    } else {
+		tentative = root;
+		root = root->left;
+	    }
+	} 
+    }
+    
+    return tentative;
+}
+
+/*
+ * Look for the node corresponding to the greatest key that is equal to or
+ * lower than the given key.  If there is no such node, return null.
+ */
+
+dnode_t *dict_upper_bound(dict_t *dict, const void *key)
+{
+    dnode_t *root = dict_root(dict);
+    dnode_t *nil = dict_nil(dict);
+    dnode_t *tentative = 0;
+
+    while (root != nil) {
+#ifdef BE_QSORT_COMPATIBLE
+	int result = dict->compare(&key, &root->key);
+#else
+	int result = dict->compare(key, root->key);
+#endif
+
+	if (result < 0) {
+	    root = root->left;
+	} else if (result > 0) {
+	    tentative = root;
+	    root = root->right;
+	} else {
+	    if (!dict->dupes) {
+	    	return root;
+	    } else {
+		tentative = root;
+		root = root->right;
+	    }
+	} 
+    }
+    
+    return tentative;
+}
+
+/*
+ * Insert a node into the dictionary. The node should have been
+ * initialized with a data field. All other fields are ignored.
+ * The behavior is undefined if the user attempts to insert into
+ * a dictionary that is already full (for which the dict_isfull()
+ * function returns true).
+ */
+
+void dict_insert(dict_t *dict, dnode_t *node, const void *key)
+{
+    dnode_t *where = dict_root(dict), *nil = dict_nil(dict);
+    dnode_t *parent = nil, *uncle, *grandpa;
+    int result = -1;
+
+    node->key = key;
+
+    assert (!dict_isfull(dict));
+    assert (!dict_contains(dict, node));
+    assert (!dnode_is_in_a_dict(node));
+
+    /* basic binary tree insert */
+
+    while (where != nil) {
+	parent = where;
+#ifdef BE_QSORT_COMPATIBLE
+	result = dict->compare(&key, &where->key);
+#else
+	result = dict->compare(key, where->key);
+#endif
+	/* trap attempts at duplicate key insertion unless it's explicitly allowed */
+	assert (dict->dupes || result != 0);
+	if (result < 0)
+	    where = where->left;
+	else
+	    where = where->right;
+    }
+
+    assert (where == nil);
+
+    if (result < 0)
+	parent->left = node;
+    else
+	parent->right = node;
+
+    node->parent = parent;
+    node->left = nil;
+    node->right = nil;
+
+    dict->nodecount++;
+
+    /* red black adjustments */
+
+    node->color = dnode_red;
+
+    while (parent->color == dnode_red) {
+	grandpa = parent->parent;
+	if (parent == grandpa->left) {
+	    uncle = grandpa->right;
+	    if (uncle->color == dnode_red) {	/* red parent, red uncle */
+		parent->color = dnode_black;
+		uncle->color = dnode_black;
+		grandpa->color = dnode_red;
+		node = grandpa;
+		parent = grandpa->parent;
+	    } else {				/* red parent, black uncle */
+	    	if (node == parent->right) {
+		    rotate_left(parent);
+		    parent = node;
+		    assert (grandpa == parent->parent);
+		    /* rotation between parent and child preserves grandpa */
+		}
+		parent->color = dnode_black;
+		grandpa->color = dnode_red;
+		rotate_right(grandpa);
+		break;
+	    }
+	} else { 	/* symmetric cases: parent == parent->parent->right */
+	    uncle = grandpa->left;
+	    if (uncle->color == dnode_red) {
+		parent->color = dnode_black;
+		uncle->color = dnode_black;
+		grandpa->color = dnode_red;
+		node = grandpa;
+		parent = grandpa->parent;
+	    } else {
+	    	if (node == parent->left) {
+		    rotate_right(parent);
+		    parent = node;
+		    assert (grandpa == parent->parent);
+		}
+		parent->color = dnode_black;
+		grandpa->color = dnode_red;
+		rotate_left(grandpa);
+		break;
+	    }
+	}
+    }
+
+    dict_root(dict)->color = dnode_black;
+
+    assert (dict_verify(dict));
+}
+
+/*
+ * Delete the given node from the dictionary. If the given node does not belong
+ * to the given dictionary, undefined behavior results.  A pointer to the
+ * deleted node is returned.
+ */
+
+dnode_t *dict_delete(dict_t *dict, dnode_t *delete)
+{
+    dnode_t *nil = dict_nil(dict), *child, *delparent = delete->parent;
+
+    /* basic deletion */
+
+    assert (!dict_isempty(dict));
+    assert (dict_contains(dict, delete));
+
+    /*
+     * If the node being deleted has two children, then we replace it with its
+     * successor (i.e. the leftmost node in the right subtree.) By doing this,
+     * we avoid the traditional algorithm under which the successor's key and
+     * value *only* move to the deleted node and the successor is spliced out
+     * from the tree. We cannot use this approach because the user may hold
+     * pointers to the successor, or nodes may be inextricably tied to some
+     * other structures by way of embedding, etc. So we must splice out the
+     * node we are given, not some other node, and must not move contents from
+     * one node to another behind the user's back.
+     */
+
+    if (delete->left != nil && delete->right != nil) {
+	dnode_t *next = dict_next(dict, delete);
+	dnode_t *nextparent = next->parent;
+	dnode_color_t nextcolor = next->color;
+
+	assert (next != nil);
+	assert (next->parent != nil);
+	assert (next->left == nil);
+
+	/*
+	 * First, splice out the successor from the tree completely, by
+	 * moving up its right child into its place.
+	 */
+
+	child = next->right;
+	child->parent = nextparent;
+
+	if (nextparent->left == next) {
+	    nextparent->left = child;
+	} else {
+	    assert (nextparent->right == next);
+	    nextparent->right = child;
+	}
+
+	/*
+	 * Now that the successor has been extricated from the tree, install it
+	 * in place of the node that we want deleted.
+	 */
+
+	next->parent = delparent;
+	next->left = delete->left;
+	next->right = delete->right;
+	next->left->parent = next;
+	next->right->parent = next;
+	next->color = delete->color;
+	delete->color = nextcolor;
+
+	if (delparent->left == delete) {
+	    delparent->left = next;
+	} else {
+	    assert (delparent->right == delete);
+	    delparent->right = next;
+	}
+
+    } else {
+	assert (delete != nil);
+	assert (delete->left == nil || delete->right == nil);
+
+	child = (delete->left != nil) ? delete->left : delete->right;
+
+	child->parent = delparent = delete->parent;	    
+
+	if (delete == delparent->left) {
+	    delparent->left = child;    
+	} else {
+	    assert (delete == delparent->right);
+	    delparent->right = child;
+	}
+    }
+
+    delete->parent = NULL;
+    delete->right = NULL;
+    delete->left = NULL;
+
+    dict->nodecount--;
+
+    assert (verify_bintree(dict));
+
+    /* red-black adjustments */
+
+    if (delete->color == dnode_black) {
+	dnode_t *parent, *sister;
+
+	dict_root(dict)->color = dnode_red;
+
+	while (child->color == dnode_black) {
+	    parent = child->parent;
+	    if (child == parent->left) {
+		sister = parent->right;
+		assert (sister != nil);
+		if (sister->color == dnode_red) {
+		    sister->color = dnode_black;
+		    parent->color = dnode_red;
+		    rotate_left(parent);
+		    sister = parent->right;
+		    assert (sister != nil);
+		}
+		if (sister->left->color == dnode_black
+			&& sister->right->color == dnode_black) {
+		    sister->color = dnode_red;
+		    child = parent;
+		} else {
+		    if (sister->right->color == dnode_black) {
+			assert (sister->left->color == dnode_red);
+			sister->left->color = dnode_black;
+			sister->color = dnode_red;
+			rotate_right(sister);
+			sister = parent->right;
+			assert (sister != nil);
+		    }
+		    sister->color = parent->color;
+		    sister->right->color = dnode_black;
+		    parent->color = dnode_black;
+		    rotate_left(parent);
+		    break;
+		}
+	    } else {	/* symmetric case: child == child->parent->right */
+		assert (child == parent->right);
+		sister = parent->left;
+		assert (sister != nil);
+		if (sister->color == dnode_red) {
+		    sister->color = dnode_black;
+		    parent->color = dnode_red;
+		    rotate_right(parent);
+		    sister = parent->left;
+		    assert (sister != nil);
+		}
+		if (sister->right->color == dnode_black
+			&& sister->left->color == dnode_black) {
+		    sister->color = dnode_red;
+		    child = parent;
+		} else {
+		    if (sister->left->color == dnode_black) {
+			assert (sister->right->color == dnode_red);
+			sister->right->color = dnode_black;
+			sister->color = dnode_red;
+			rotate_left(sister);
+			sister = parent->left;
+			assert (sister != nil);
+		    }
+		    sister->color = parent->color;
+		    sister->left->color = dnode_black;
+		    parent->color = dnode_black;
+		    rotate_right(parent);
+		    break;
+		}
+	    }
+	}
+
+	child->color = dnode_black;
+	dict_root(dict)->color = dnode_black;
+    }
+
+    assert (dict_verify(dict));
+
+    return delete;
+}
+
+/*
+ * Allocate a node using the dictionary's allocator routine, give it
+ * the data item.
+ */
+
+int dict_alloc_insert(dict_t *dict, const void *key, void *data)
+{
+    dnode_t *node = dict->allocnode(dict->context);
+
+    if (node) {
+	dnode_init(node, data);
+	dict_insert(dict, node, key);
+	return 1;
+    }
+    return 0;
+}
+
+void dict_delete_free(dict_t *dict, dnode_t *node)
+{
+    dict_delete(dict, node);
+    dict->freenode(node, dict->context);
+}
+
+/*
+ * Return the node with the lowest (leftmost) key. If the dictionary is empty
+ * (that is, dict_isempty(dict) returns 1) a null pointer is returned.
+ */
+
+dnode_t *dict_first(dict_t *dict)
+{
+    dnode_t *nil = dict_nil(dict), *root = dict_root(dict), *left;
+
+    if (root != nil)
+	while ((left = root->left) != nil)
+	    root = left;
+
+    return (root == nil) ? NULL : root;
+}
+
+/*
+ * Return the node with the highest (rightmost) key. If the dictionary is empty
+ * (that is, dict_isempty(dict) returns 1) a null pointer is returned.
+ */
+
+dnode_t *dict_last(dict_t *dict)
+{
+    dnode_t *nil = dict_nil(dict), *root = dict_root(dict), *right;
+
+    if (root != nil)
+	while ((right = root->right) != nil)
+	    root = right;
+
+    return (root == nil) ? NULL : root;
+}
+
+/*
+ * Return the given node's successor node---the node which has the
+ * next key in the the left to right ordering. If the node has
+ * no successor, a null pointer is returned rather than a pointer to
+ * the nil node.
+ */
+
+dnode_t *dict_next(dict_t *dict, dnode_t *curr)
+{
+    dnode_t *nil = dict_nil(dict), *parent, *left;
+
+    if (curr->right != nil) {
+	curr = curr->right;
+	while ((left = curr->left) != nil)
+	    curr = left;
+	return curr;
+    }
+
+    parent = curr->parent;
+
+    while (parent != nil && curr == parent->right) {
+	curr = parent;
+	parent = curr->parent;
+    }
+
+    return (parent == nil) ? NULL : parent;
+}
+
+/*
+ * Return the given node's predecessor, in the key order.
+ * The nil sentinel node is returned if there is no predecessor.
+ */
+
+dnode_t *dict_prev(dict_t *dict, dnode_t *curr)
+{
+    dnode_t *nil = dict_nil(dict), *parent, *right;
+
+    if (curr->left != nil) {
+	curr = curr->left;
+	while ((right = curr->right) != nil)
+	    curr = right;
+	return curr;
+    }
+
+    parent = curr->parent;
+
+    while (parent != nil && curr == parent->left) {
+	curr = parent;
+	parent = curr->parent;
+    }
+
+    return (parent == nil) ? NULL : parent;
+}
+
+void dict_allow_dupes(dict_t *dict)
+{
+    dict->dupes = 1;
+}
+
+#undef dict_count
+#undef dict_isempty
+#undef dict_isfull
+#undef dnode_get
+#undef dnode_put
+#undef dnode_getkey
+
+dictcount_t dict_count(dict_t *dict)
+{
+    return dict->nodecount;
+}
+
+int dict_isempty(dict_t *dict)
+{
+    return dict->nodecount == 0;
+}
+
+int dict_isfull(dict_t *dict)
+{
+    return dict->nodecount == dict->maxcount;
+}
+
+int dict_contains(dict_t *dict, dnode_t *node)
+{
+    return verify_dict_has_node(dict_nil(dict), dict_root(dict), node);
+}
+
+static dnode_t *dnode_alloc(void *context)
+{
+    return malloc(sizeof *dnode_alloc(NULL));
+}
+
+static void dnode_free(dnode_t *node, void *context)
+{
+    free(node);
+}
+
+dnode_t *dnode_create(void *data)
+{
+    dnode_t *new = malloc(sizeof *new);
+    if (new) {
+	new->data = data;
+	new->parent = NULL;
+	new->left = NULL;
+	new->right = NULL;
+    }
+    return new;
+}
+
+dnode_t *dnode_init(dnode_t *dnode, void *data)
+{
+    dnode->data = data;
+    dnode->parent = NULL;
+    dnode->left = NULL;
+    dnode->right = NULL;
+    return dnode;
+}
+
+void dnode_destroy(dnode_t *dnode)
+{
+    assert (!dnode_is_in_a_dict(dnode));
+    free(dnode);
+}
+
+void *dnode_get(dnode_t *dnode)
+{
+    return dnode->data;
+}
+
+const void *dnode_getkey(dnode_t *dnode)
+{
+    return dnode->key;
+}
+
+void dnode_put(dnode_t *dnode, void *data)
+{
+    dnode->data = data;
+}
+
+int dnode_is_in_a_dict(dnode_t *dnode)
+{
+    return (dnode->parent && dnode->left && dnode->right);
+}
+
+void dict_process(dict_t *dict, void *context, dnode_process_t function)
+{
+    dnode_t *node = dict_first(dict), *next;
+
+    while (node != NULL) {
+	/* check for callback function deleting	*/
+	/* the next node from under us		*/
+	assert (dict_contains(dict, node));
+	next = dict_next(dict, node);
+	function(dict, node, context);
+	node = next;
+    }
+}
+
+static void load_begin_internal(dict_load_t *load, dict_t *dict)
+{
+    load->dictptr = dict;
+    load->nilnode.left = &load->nilnode;
+    load->nilnode.right = &load->nilnode;
+}
+
+void dict_load_begin(dict_load_t *load, dict_t *dict)
+{
+    assert (dict_isempty(dict));
+    load_begin_internal(load, dict);
+}
+
+void dict_load_next(dict_load_t *load, dnode_t *newnode, const void *key)
+{
+    dict_t *dict = load->dictptr;
+    dnode_t *nil = &load->nilnode;
+   
+    assert (!dnode_is_in_a_dict(newnode));
+    assert (dict->nodecount < dict->maxcount);
+
+#ifndef NDEBUG
+    if (dict->nodecount > 0) {
+#ifdef BE_QSORT_COMPATIBLE
+	if (dict->dupes)
+	    assert (dict->compare(&nil->left->key, &key) <= 0);
+	else
+	    assert (dict->compare(&nil->left->key, &key) < 0);
+#else
+	if (dict->dupes)
+	    assert (dict->compare(nil->left->key, key) <= 0);
+	else
+	    assert (dict->compare(nil->left->key, key) < 0);
+#endif
+    }
+#endif
+
+    newnode->key = key;
+    nil->right->left = newnode;
+    nil->right = newnode;
+    newnode->left = nil;
+    dict->nodecount++;
+}
+
+void dict_load_end(dict_load_t *load)
+{
+    dict_t *dict = load->dictptr;
+    dnode_t *tree[DICT_DEPTH_MAX] = { 0 };
+    dnode_t *curr, *dictnil = dict_nil(dict), *loadnil = &load->nilnode, *next;
+    dnode_t *complete = 0;
+    dictcount_t fullcount = DICTCOUNT_T_MAX, nodecount = dict->nodecount;
+    dictcount_t botrowcount;
+    unsigned baselevel = 0, level = 0, i;
+
+    assert (dnode_red == 0 && dnode_black == 1);
+
+    while (fullcount >= nodecount && fullcount)
+	fullcount >>= 1;
+
+    botrowcount = nodecount - fullcount;
+
+    for (curr = loadnil->left; curr != loadnil; curr = next) {
+	next = curr->left;
+
+	if (complete == NULL && botrowcount-- == 0) {
+	    assert (baselevel == 0);
+	    assert (level == 0);
+	    baselevel = level = 1;
+	    complete = tree[0];
+
+	    if (complete != 0) {
+		tree[0] = 0;
+		complete->right = dictnil;
+		while (tree[level] != 0) {
+		    tree[level]->right = complete;
+		    complete->parent = tree[level];
+		    complete = tree[level];
+		    tree[level++] = 0;
+		}
+	    }
+	}
+
+	if (complete == NULL) {
+	    curr->left = dictnil;
+	    curr->right = dictnil;
+	    curr->color = level % 2;
+	    complete = curr;
+
+	    assert (level == baselevel);
+	    while (tree[level] != 0) {
+		tree[level]->right = complete;
+		complete->parent = tree[level];
+		complete = tree[level];
+		tree[level++] = 0;
+	    }
+	} else {
+	    curr->left = complete;
+	    curr->color = (level + 1) % 2;
+	    complete->parent = curr;
+	    tree[level] = curr;
+	    complete = 0;
+	    level = baselevel;
+	}
+    }
+
+    if (complete == NULL)
+	complete = dictnil;
+
+    for (i = 0; i < DICT_DEPTH_MAX; i++) {
+	if (tree[i] != 0) {
+	    tree[i]->right = complete;
+	    complete->parent = tree[i];
+	    complete = tree[i];
+	}
+    }
+
+    dictnil->color = dnode_black;
+    dictnil->right = dictnil;
+    complete->parent = dictnil;
+    complete->color = dnode_black;
+    dict_root(dict) = complete;
+
+    assert (dict_verify(dict));
+}
+
+void dict_merge(dict_t *dest, dict_t *source)
+{
+    dict_load_t load;
+    dnode_t *leftnode = dict_first(dest), *rightnode = dict_first(source);
+
+    assert (dict_similar(dest, source));	
+
+    if (source == dest)
+	return;
+
+    dest->nodecount = 0;
+    load_begin_internal(&load, dest);
+
+    for (;;) {
+	if (leftnode != NULL && rightnode != NULL) {
+#ifdef BE_QSORT_COMPATIBLE
+	    if (dest->compare(&leftnode->key, &rightnode->key) < 0)
+		goto copyleft;
+	    else
+		goto copyright;
+#else
+	    if (dest->compare(leftnode->key, rightnode->key) < 0)
+		goto copyleft;
+	    else
+		goto copyright;
+#endif
+	} else if (leftnode != NULL) {
+	    goto copyleft;
+	} else if (rightnode != NULL) {
+	    goto copyright;
+	} else {
+	    assert (leftnode == NULL && rightnode == NULL);
+	    break;
+	}
+
+    copyleft:
+	{
+	    dnode_t *next = dict_next(dest, leftnode);
+	#ifndef NDEBUG
+	    leftnode->left = NULL;	/* suppress assertion in dict_load_next */
+	#endif
+	    dict_load_next(&load, leftnode, leftnode->key);
+	    leftnode = next;
+	    continue;
+	}
+	
+    copyright:
+	{
+	    dnode_t *next = dict_next(source, rightnode);
+#ifndef NDEBUG
+	    rightnode->left = NULL;
+#endif
+	    dict_load_next(&load, rightnode, rightnode->key);
+	    rightnode = next;
+	    continue;
+	}
+    }
+
+    dict_clear(source);
+    dict_load_end(&load);
+}
diff --git a/libutil/kazlib/dict.h b/libutil/kazlib/dict.h
new file mode 100644
index 0000000..2bab634
--- /dev/null
+++ b/libutil/kazlib/dict.h
@@ -0,0 +1,142 @@
+/*
+ * Dictionary Abstract Data Type
+ * Copyright (C) 1997 Kaz Kylheku <kaz at ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ */
+
+#ifndef DICT_H
+#define DICT_H
+
+#include <limits.h>
+#ifdef KAZLIB_SIDEEFFECT_DEBUG
+#include "sfx.h"
+#endif
+
+/*
+ * Blurb for inclusion into C++ translation units
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef unsigned long dictcount_t;
+#define DICTCOUNT_T_MAX ULONG_MAX
+
+/*
+ * The dictionary is implemented as a red-black tree
+ */
+
+typedef enum { dnode_red, dnode_black } dnode_color_t;
+
+typedef struct dnode_t {
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+    struct dnode_t *dict_left;
+    struct dnode_t *dict_right;
+    struct dnode_t *dict_parent;
+    dnode_color_t dict_color;
+    const void *dict_key;
+    void *dict_data;
+#else
+    int dict_dummy;
+#endif
+} dnode_t;
+
+typedef int (*dict_comp_t)(const void *, const void *);
+typedef dnode_t *(*dnode_alloc_t)(void *);
+typedef void (*dnode_free_t)(dnode_t *, void *);
+
+typedef struct dict_t {
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+    dnode_t dict_nilnode;
+    dictcount_t dict_nodecount;
+    dictcount_t dict_maxcount;
+    dict_comp_t dict_compare;
+    dnode_alloc_t dict_allocnode;
+    dnode_free_t dict_freenode;
+    void *dict_context;
+    int dict_dupes;
+#else
+    int dict_dummmy;
+#endif
+} dict_t;
+
+typedef void (*dnode_process_t)(dict_t *, dnode_t *, void *);
+
+typedef struct dict_load_t {
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+    dict_t *dict_dictptr;
+    dnode_t dict_nilnode;
+#else
+    int dict_dummmy;
+#endif
+} dict_load_t;
+
+extern dict_t *dict_create(dictcount_t, dict_comp_t);
+extern void dict_set_allocator(dict_t *, dnode_alloc_t, dnode_free_t, void *);
+extern void dict_destroy(dict_t *);
+extern void dict_free_nodes(dict_t *);
+extern void dict_free(dict_t *);
+extern dict_t *dict_init(dict_t *, dictcount_t, dict_comp_t);
+extern void dict_init_like(dict_t *, const dict_t *);
+extern int dict_verify(dict_t *);
+extern int dict_similar(const dict_t *, const dict_t *);
+extern dnode_t *dict_lookup(dict_t *, const void *);
+extern dnode_t *dict_lower_bound(dict_t *, const void *);
+extern dnode_t *dict_upper_bound(dict_t *, const void *);
+extern void dict_insert(dict_t *, dnode_t *, const void *);
+extern dnode_t *dict_delete(dict_t *, dnode_t *);
+extern int dict_alloc_insert(dict_t *, const void *, void *);
+extern void dict_delete_free(dict_t *, dnode_t *);
+extern dnode_t *dict_first(dict_t *);
+extern dnode_t *dict_last(dict_t *);
+extern dnode_t *dict_next(dict_t *, dnode_t *);
+extern dnode_t *dict_prev(dict_t *, dnode_t *);
+extern dictcount_t dict_count(dict_t *);
+extern int dict_isempty(dict_t *);
+extern int dict_isfull(dict_t *);
+extern int dict_contains(dict_t *, dnode_t *);
+extern void dict_allow_dupes(dict_t *);
+extern int dnode_is_in_a_dict(dnode_t *);
+extern dnode_t *dnode_create(void *);
+extern dnode_t *dnode_init(dnode_t *, void *);
+extern void dnode_destroy(dnode_t *);
+extern void *dnode_get(dnode_t *);
+extern const void *dnode_getkey(dnode_t *);
+extern void dnode_put(dnode_t *, void *);
+extern void dict_process(dict_t *, void *, dnode_process_t);
+extern void dict_load_begin(dict_load_t *, dict_t *);
+extern void dict_load_next(dict_load_t *, dnode_t *, const void *);
+extern void dict_load_end(dict_load_t *);
+extern void dict_merge(dict_t *, dict_t *);
+
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+#ifdef KAZLIB_SIDEEFFECT_DEBUG
+#define dict_isfull(D) (SFX_CHECK(D)->dict_nodecount == (D)->dict_maxcount)
+#else
+#define dict_isfull(D) ((D)->dict_nodecount == (D)->dict_maxcount)
+#endif
+#define dict_count(D) ((D)->dict_nodecount)
+#define dict_isempty(D) ((D)->dict_nodecount == 0)
+#define dnode_get(N) ((N)->dict_data)
+#define dnode_getkey(N) ((N)->dict_key)
+#define dnode_put(N, X) ((N)->dict_data = (X))
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libutil/kazlib/docs/CHANGES b/libutil/kazlib/docs/CHANGES
new file mode 100644
index 0000000..3c949eb
--- /dev/null
+++ b/libutil/kazlib/docs/CHANGES
@@ -0,0 +1,290 @@
+New in 1.20
+
+    1. Bugfix in except.h. Modified non-volatile auto variables were
+       being accessed after longjmp.
+
+New in 1.19
+
+    1. Rewrite of broken dict_free.
+    2. Fixed embarassing build breakages that accidentally went into 1.18
+    3. Function hash_scan_delete_free renamed to hash_scan_delfree to be
+       distinct from hash_scan_delete in the first 14 characters.
+    4. To resolve inconsistencies between hash_free and dict_free,
+       and a difference between the actual behavior of hash_free  and
+       the documented behavior, these two functions are marked obsolescent.
+       The functions dict_free_nodes and hash_free_nodes are provided.
+       The obsolescent functions continue to work as before, for now.
+    5. Documentation of hash_free is fixed to say that it also subjects
+       the hash to hash_destroy, which is what the implementation does.
+    6. Documentation states what release it is for.
+
+New in 1.18
+
+    1. Error in assert expression in list_merge fixed.
+    2. Semantics of list_merge extended to allow list to be merged
+       onto itself (which is a noop).
+    3. Clarified interface specification of list_transfer and list_extract;
+       the source and destination list may be the same object.
+    4. New functions:
+       dict_init_like: create a dictionary similar to another one;
+       dict_similar: determine whether two dictionaries are similar;
+       dict_merge: merge contents of one dictionary to another.
+    5. Dictionary test main can juggle multiple dictionaries, and test
+       dict_merge. 
+    6. If a hash node is inserted into some hash, it is a now a constraint
+       violation to insert it again into some hash.
+    7. The hash_scan_delete_free function has been implemented; it is to
+       hash_scan_delete what hash_delete_free is to hash_delete.
+
+New in 1.17
+
+    Carl van Tast <vanTast at netway.at>:
+    1. Removed references to ``safe malloc'' from some comments.
+    2. Swapped ``allowed'' and ``not allowed'' in comment to
+       verify_bintree.
+    3. Fixed comment to list_next: this function never returns the
+       sentinel.
+    4. lnode_pool_init: nodes[i].prev = nodes instead of nodes + 1. This
+       saves one or two CPU cycles :-) and it gives a valid address even
+       if we have a (somewhat pathological) pool with just one element.
+
+    Kaz:
+    5. Dropped extra parameter from tree rotation functions in dict.c. Should
+       shave a few cycles.
+    6. Fixed error in the duplicate key iteration idiom example in the
+       documentation (see the section on dict_upper_bound).
+    7. Forgotten #include <string.h> added to hash.c
+
+New in 1.16
+
+    1. Added an interface for loading the contents of a dictionary from an
+       ordered sequence. This is done in O(n) time by a direct bottom-up
+       construction of the red-black tree, making it much faster than
+       the O(n log n) process of inserting each element.
+    2. Miscellaneous cleanup: missing const qualifiers were added
+       to key pointer parameters, some incorrect comments fixed;
+       spelling errors corrected in documentation.
+
+New in 1.15
+
+    1. Another potential exception handling memory leak fixed. This one
+       has to do with throwing an exception from within a try-catch region
+       in which an exception was just caught. The new exception replaces
+       the old without the old's dynamic memory being disposed of.
+    2. Restrictions added on except_rethrow.
+    3. Exception module must now be explicitly initialized with except_init.
+    4. Structure members in exception header renamed to adhere to documented
+       namespace.
+    5. The exwrap.[ch] source files are gone. There is support for memory
+       allocation with exception handling in except.c, which supports user
+       defined allocators.
+    6. Three bugfixes to sfx parser. First, unary operators take a cast
+       expression, not a unary expression. Secondly, sizeof doesn't throw a syntax
+       error anymore on things that look like casts, but maybe are not.
+       Thirdly, empty parentheses weren't handled right in treatment of
+       ambiguous expressions, e.g. (a)() was declared a syntax error.
+    7. Changed the representation of hash table chains. They are now
+       singly linked lists, which means that the overhead of managing 
+       back pointers is gone. Only deletion is slightly more complicated
+       now because it has to search from the beginning of the chain.
+       [Rationale: this is okay, since chains are supposed to be short
+       in a hash table!]
+    8. Rewritten test main() in list.c. It's now more like the others
+       with a menu. Previously it was essentially a file sorting program.
+    9. New function: list_find. Exhaustively searches the list for a
+       matching entry, returns pointer to node if found.
+
+New in 1.14
+
+    1. Got rid of some overbearing copyright restrictions. There is no need for
+       executables to contain copyright notices. In fact, there are no
+       restrictions on the use, or distribution in executable form.
+    2. Tiny tweak in red-black fixup code of dict_insert.
+    3. Keys in hash and dict are declared const void * now in all functions
+       rather than plain void *.  This means that casts are no longer
+       necessary when calling insert or lookup functions with const
+       data as the key. But casts of the return value of hnode_getkey
+       or dnode_getkey may be required.
+    4. Fixed compile breakage of except.c when posix thread support enabled.
+    5. Side effect assertion interface now performs caching, to avoid
+       parsing the same expressions over and over again.  Thus debugging with
+       KAZLIB_SIDEEFFECT_DEBUG incurs a smaller performance hit.
+    6. Major bugfix to sfx expression parser. The function dealing with 
+       disambiguating casts had to be rewritten to do more sophisticated
+       lookahead and backtracking. It all started with Mark Brady discovered
+       that (a++)+b was being incorrectly diagnosed as a syntax error.
+    7. Added documentation. more examples for uses of dictionaries, and
+       exception handling. Some documentation about the internals
+       of exception handling added. Changed document format for narrower
+       margins, reducing page count and increasing readability.
+    8. Bugfix in except_rethrow. It was freeing the dynamic data of the
+       exception even though it's not handled yet.
+
+New in 1.13
+
+    1. Fixed some potential memory leaks in except.c.
+    2. Finished all interface documentation. All that is left now
+       is to flesh out the implementation notes.
+    3. Fixed a bug in POSIX threaded variant of except.c. Null
+       function pointer dereference in unhandled exception case.
+    4. Macros beginning with E[A-Z] have been renamed to stay out
+       of space reserved for <errno.h>.
+    5. Identifiers in exwrap.[ch] have been renamed from having 
+       ex_ prefixed to having exwrap_ prefixes.
+
+New in 1.12
+
+    1. COOL! New module for detecting side effects in C expressions.
+    2. Serious bugfix in hash_init().  The computation of the initial hash
+       mask was completely botched up. Historically this code has seen little
+       testing because hashing over a user supplied table is not extendible. 
+       Users of hash_create() are not affected.
+    3. Tried to make computation of hash_val_t_bit more threadsafe. It should
+       be okay if writes to int objects are atomic, and concurrent writes of
+       the same int value to a given object are safe.
+    4. Makefile renamed to Makefile.gcc. Makevile.vc added. The rename
+       is retroactive to all prior releases.
+    5. OPAQUE_DEBUG becomes KAZLIB_OPAQUE_DEBUG and TEST_MAIN becomes
+       KAZLIB_TEST_MAIN. In general, macros that affect how the modules
+       build should be confined to a special namespace.
+    6. New KAZLIB_SIDEEFFECT_DEBUG feature to enable diagnosis of side
+       effect expressions being passed to macros that evaluate their arguments
+       more than once.
+
+New in 1.11
+
+    1. Improvements in experimental exception handling module:
+       except_throwf has been added which takes printf-like arguments;
+       except_checked_cleanup_pop has been added to provide a measure
+       of safety; there is now a way to pass arbitrary data from the throw site
+       to the catch.
+    2. Improvements in dict_insert. A redundant call to the comparison function
+       has been eliminated, resulting in one fewer comparisons per insert
+       operation! Also a redundant test has been removed from the controlling
+       expression of the fixup loop, taking advantage of the fact that nil
+       is always black, and hence the root node always has a black parent.
+    3. Small change in dict_delete. A test in the fixup loop has been eliminated
+       by temporarily coloring the root node red. See comment and diff between
+       dict.c revision 1.25 and 1.26.
+    4. Test program blast.pl deletes keys out of order; to get in order
+       delete, initialize $factor_d to 1.
+
+New in 1.10
+
+    1. The dict_init function now correctly initializes allocator-related
+       members of the dict structure.
+    2. Tiny optimization in dict_lookup---less frequent cases tested last.
+    3. Added list_extract, for extracting list slices (more general than
+       list_transfer).
+    4. Incorporated changes from Loic Dachary: hash_free() has been
+       added for deleting all nodes; hash and compare functions
+       from the hash.c test code are now available to the user as
+       defaults if null pointers are given to hash_init() or
+       hash_create(); and hash_set_allocator restores the default
+       allocator routines if null pointers are given to it.
+    5. Changes to dict analogous to hash: dict_free() added, etc.
+    6. New exception handling module added (experimental).
+    7. Much new documentation.
+
+New in 1.9
+
+    1. Third argument of list_transfer may be null, in which case no nodes
+       are transferred. [Rationale: allows empty source list to be treated
+       without special case testing when all nodes are being transferred.]
+    2. Two new functions added to dict: dict_upper_bound and dict_lower_bound.
+       These allow for inexact and range searches.
+
+New in 1.8
+
+    1. New improved hashing function in the hash.c test code. It turns out that
+       when I changed the hash table algorithm, the blast.pl testcase was
+       hashing all to a single chain due to the pathologically bad hashing
+       function.  The new hashing function should be good enough for general use.
+       It uses each nybble of the key to index a table of 16 random 32 bit integers.
+       These integers are XOR-ed into the hash value which is rotated after each
+       XOR.
+    2. Spurious semicolon removed from the #define of HASH_VAL_T_BIT.
+    3. I fixed some incorrect comments in hash.c which still talked about the
+       old algorithm from release 1.5 and older.
+    4. The smalloc.c module is no longer supported. It's still in RCS but it's not
+       tagged as being part of release 1.8, and is not used by any of the other
+       sources. The standard library memory allocation functions are now used
+       directly. [Rationale: smalloc.c is overkill and interferes with
+       integration of the other source files into projects. Conscientious programmer
+       already ahve their own tools for debugging allocator corruption, anyway.]
+
+New in 1.7
+
+    1. Missing #include <stdlib.h> added to smalloc.h
+    2. The dict_delete() functions internals have been changed to make it much
+       more sane. This function no longer has the potential to return a node
+       other than the one that is passed to it.
+    3. The changes to dict_delete() also fix a serious bug in dict_process().
+       The dict_process computes a pointer to a node's successor before
+       invoking the user callback to process a node. If the user callback calls
+       dict_delete() on the node, under the old dict_delete() semantics it was
+       possible for the successor to get deleted instead. Thus dict_process()
+       could end up with an invalid pointer.
+    4. The changes to dict_delete() also mean that key and value information will
+       never be relocated from one node to another. User code can now rely on this
+       convenient assumption.
+
+New in 1.6
+
+    1. The extendible hashing algorithm internals have changed. This
+       has a potential impact on the behavior with respect to hashing functions
+       which were written to work well specifically with the old hashing
+       scheme. For a silly reason, in the old hashing scheme, the top N bits
+       were always taken from the results of a hashing function, for a hash
+       table size of 2^N chains. In the new scheme, the bottom N bits are taken
+       instead. [Rationale: This is change makes it easier to write portable
+       hashing functions and simplifies the functions that expand or contract
+       the table, making them more efficient.]
+    2. Added const qualifiers to the rcsid[] and right[] char arrays,
+       which shuts up the GCC compiler from complaining that these are
+       unused statics.
+
+New in 1.5
+
+    1. First two arguments to list_prune_graft() are reversed. The leftmost
+       argument is now the destination list. Moreover, the function has been
+       renamed list_transfer(). [Rationale: this ordering of parameters is
+       consistent with list_merge(), and the standard C <string.h> functions
+       also pass destination pointers on the left.  Renaming the function
+       protects against incorrect use.]
+
+    2. Red-Black tree dictionaries now support duplicate keys.  [Rationale:
+       duplicate keys could be useful in some applications.] When a dictionary
+       is created or initialized, it does not allow duplicate keys. The
+       function dict_allow_dupes() is used to set a flag in a dictionary to
+       henceforth allow duplicates.  Once made, the decision to allow
+       duplicates cannot be reversed.  [Rationale: toggling between allowing
+       and disallowing duplicates does not seem useful. Once duplicates are
+       admitted, there is no point in disallowing duplicates.] When a key is
+       sought in tree that currently allows duplicates, the leftmost node
+       containing that key is chosen from among the nodes that contain
+       duplicates of the key.  Then dict_next() can be used to fetch the
+       remaining duplicates one by one.  No particular order among the
+       duplicates may be assumed.  However, for what it may be worth, the order
+       between any two duplicates is preserved for as long as they both remain
+       in the dictionary.
+
+    3. The function prototypes in the header files have been modified to eliminate
+       parameter names.  [Rationale: parameter names in prototypes have only
+       documentary value, and may clash with macro identifiers defined in other
+       headers.]
+
+    4. Dictionary and hash table now has support for automatic allocation of
+       nodes in the insert and delete operations, which means that the user
+       can add items in one operation instead of the two operations of
+       allocating a node and inserting it. [Rationale: ease of use.] There is
+       support for user-defined allocators; the default allocators use the
+       smalloc.c routines. For any instance of a dict_t or hash_t object, the
+       user can override the allocator functions by supplying his or her
+       own pointers to suitable functions, and a context pointer that 
+       will be passed to these functions when they are called through that
+       particular dict_t or hash_t instance. [Rationale: flexibility, ease of
+       use, promotes good design.] The funtion pointers can only be set when
+       the data structure is empty. [Rationale: it is undesirable to switch to
+       a different allocator when there are nodes in the dictionary; it might
+       lead to the error of freeing a node with an incorrect allocator.]
diff --git a/libutil/kazlib/docs/MUST_READ b/libutil/kazlib/docs/MUST_READ
new file mode 100644
index 0000000..20ca12e
--- /dev/null
+++ b/libutil/kazlib/docs/MUST_READ
@@ -0,0 +1,25 @@
+Greetings, Programmer!
+
+I gather that because you are reading this, you are probably considering using
+the C language translation units included here in your own software.   If that
+is the case, I would like to know who you are and urge you to contact me.
+
+Here is why: I rove over this code periodically looking for defects. In fact,
+I use it in my own programming projects.  If I discover a defect, I will
+notify everyone who I know is a user of this software. If there is a serious
+defect in some code that you are using in your software project, wouldn't you
+want to be informed? In fact, there is no question that you _need_ to be
+informed!
+
+Here is what you do: simply send an e-mail message to kaz at ashi.footprints.net
+with the subject "kazlib" and the body "I am a user". Be sure that your message
+has a good return address. I will manually add your e-mail address to a list
+which I will use only for the purpose of notifications regarding Kazlib.   You
+will receive a reply to the effect that you are added. 
+
+If ever you should wish to be removed from this list, simply ask and it shall
+be done.
+
+Yours in earnest,
+
+    Kaz Kylheku
diff --git a/libutil/kazlib/docs/README b/libutil/kazlib/docs/README
new file mode 100644
index 0000000..08f14a1
--- /dev/null
+++ b/libutil/kazlib/docs/README
@@ -0,0 +1,66 @@
+This collection of data structures is maintained by
+Kaz Kylheku <kaz at ashi.footprints.net>
+
+INSTRUCTIONS
+
+Simply add the necessary .c and .h files to your project.  Include the
+appropriate .h file in any translation unit that interfaces with one or more of
+the kazlib modules. Then compile and link the modules together with your program.
+
+To use kazlib in a C++ project, don't compile them with a C++ compiler.
+Compile with a C compiler, and include the header files in
+your C++ translation units. Then link together the translated C and C++.
+As of release 1.2, the header files should work with C++.
+
+IMPORTANT NOTES
+
+1. Self checks
+
+The modules in this collection perform extensive self-checks, some of
+which make the performance really poor (by actually raising the overall
+asymptotic complexity of an operation, for example from O(log N) to O(N).  The
+instrumentation assertions can be disabled by compiling with the NDEBUG macro
+defined.
+
+You can check that your project does not violate the principles of
+implementation hiding in connection with its use of the kazlib modules. This
+is accomplished by defining the macro KAZLIB_OPAQUE_DEBUG at the beginning of
+any translation unit which includes the kazlib header files. Note that
+whereas this will detect violations, it will not result in a translation
+that can be linked against the kazlib. When you are done checking, turn
+off KAZLIB_OPAQUE_DEBUG and recompile. If your compiler has a special ``check only''
+mode which enables it to perform syntax and type checking without doing
+an actual translation (similar to lint), it may be a time-saving idea to
+use it in conjunction with KAZLIB_OPAQUE_DEBUG.
+
+2. Macros with side effects
+
+Some of the kazlib header files define macros that evaluate their arguments
+more than once. This means that if expressions with side effects are passed
+to these macros, undesirable and undefined behavior will happen. There is
+support in Kazlib for catching these kinds of bugs: compile with
+KAZLIB_SIDEEFFECT_DEBUG, and add the except.c and sfx.c modules to your
+object. The macros will now parse their expressions at run time to diagnose
+the presence of side effects and function calls. It's easy to add this support
+to your own code!
+
+3. Thread support
+
+POSIX thread support is enabled by predefining KAZLIB_POSIX_THREADS. Currently
+only the exception-handling module has any need for this. When compiled that
+way, it provides thread-safe exception handling. Threads can independently
+throw exceptions and each thread can install its own specific catcher
+for unhandled exceptions. Moreover, each thread can register its own
+memory allocator functions.
+
+Note: this variant of the code also depends on the ability to cast between void
+* and function pointers, which is a common language extension.
+
+4. CVS identification
+
+The source files contain declarations of a static char array variable called
+rcsid. This contains an expansion of the CVS identification of each module,
+making it possible to determine the ``bill of materials'' that went into an
+executable build. I have now wrapped the declarations of these rcsid[] arrays
+so they are conditional on KAZLIB_RCSID being defined. For many users, these
+are just a waste of space.
diff --git a/libutil/kazlib/docs/docs.ist b/libutil/kazlib/docs/docs.ist
new file mode 100644
index 0000000..808c029
--- /dev/null
+++ b/libutil/kazlib/docs/docs.ist
@@ -0,0 +1,4 @@
+preamble
+"\\begin{theindex}\n\\addcontentsline{toc}{section}{Index}\n"
+postamble
+"\n\\end{theindex}\n"
diff --git a/libutil/kazlib/docs/docs.ltx b/libutil/kazlib/docs/docs.ltx
new file mode 100644
index 0000000..139f212
--- /dev/null
+++ b/libutil/kazlib/docs/docs.ltx
@@ -0,0 +1,4155 @@
+\documentclass{article}
+\usepackage{makeidx}
+\usepackage[margin=1.0in]{geometry}
+\makeatletter
+\newcommand{\defsubsection}{\@startsection
+    {subsection}
+    {2}
+    {0pt}
+    {2.0ex plus 0.1ex minus 0.05ex}
+    {-0pt}
+    {\normalfont\normalsize\bfseries}}
+\newcommand{\defsubsubsection}{\@startsection
+    {subsection}
+    {3}
+    {0ex}
+    {2.0ex plus 0.1ex minus 0.05ex}
+    {1.0ex}
+    {\normalfont\normalsize\bfseries}}
+\renewcommand{\paragraph}{\@startsection
+    {paragraph}
+    {4}
+    {0ex}
+    {2.0ex plus 0.1ex minus 0.05ex}
+    {1.0ex}
+    {\normalsize\bfseries}}
+\makeatother
+\title{Kazlib---Reusable Components\\for C Programming}
+\author{Kaz Kylheku}
+\date{Release 1.20\\July 24, 2001}
+\makeindex
+\setcounter{tocdepth}{1}
+\setcounter{secnumdepth}{4}
+\begin{document}
+\catcode`\_=11
+\def\indextype#1{\index{#1@{\tt #1} type}}
+\def\indexmacro#1{\index{#1@{\tt #1} macro}}
+\def\indexobject#1{\index{#1@{\tt #1} object}}
+\def\indexfunc#1{\index{#1@{\tt #1} function}}
+\def\indexenum#1{\index{#1@{\tt #1} enum constant}}
+\def\synopsis{\paragraph*{Synopsis}}
+\def\constraints{\paragraph*{Constraints}}
+\def\description{\paragraph*{Description}}
+\def\example{\paragraph*{Example}}
+\maketitle
+\abstract{The aim of the Kazlib project is to provide a well-documented
+programming interface featuring commonly needed programming abstractions,
+accompanied by a high quality, portable reference implementation.
+Kazlib consists of four independent components: a list module, a hash table
+module, a dictionary module and an exception handling module. The reference
+implementations of the first three of these are based on, respectively, the
+following algorithms: doubly linked circular list with sentinel node,
+extendible hashing, and red-black tree.}
+\tableofcontents
+\section{Introduction}
+This document establishes the provisions required of an implementation of the
+Kazlib library, and describes a reference implementation thereof.
+This document specifies
+\begin{itemize}
+\item the names and types of identifiers and preprocessor symbols made
+    available by each component;
+\item identifier name spaces reserved for future use by each component;
+\item the interface syntax and semantics of each component operation;
+\item the conditions required for the well-defined execution of each operation;
+\item the externally visible behavior of each component, including global
+    side effects and the effects on the subject data structures;
+
+\item and the implementation language of Kazlib.
+\end{itemize}
+Furthermore, this document describes, but does not specify
+\begin{itemize}
+\item the implementation details of structure objects manipulated by the
+    operations of each component;
+\item objects and functions that are defined by the implementation of
+    each component but are not externally visible;
+\item the algorithms and implementation details of the operations.
+\end{itemize}
+Finally, this document does {\em not\/} specify or describe
+\begin{itemize}
+\item the specific choices for parameters which may be adjusted by an
+    installation or implementation of Kazlib.
+\item the size of any data structure which will exceed the capacity of
+    a particular installation.
+\item the mechanisms or procedures for the translation of Kazlib and
+    their integration with other translation units.
+\end{itemize}
+
+\section{References}
+\label{sec:references}
+
+\begin{trivlist}
+\item ISO 9899:1990, {\it Programming Languages---C.}
+\item {\it Introduction to Algorithms}, Thomas H. Cormen, Charles E.
+Leiserson, Ronald L. Rivest, eighth printing, 1992.
+\end{trivlist}
+
+\section{Definitions and conventions}
+The following terms shall be interpreted in accordance with the definitions
+below. Other terms appearing in this document shall be defined upon their
+first mention, indicated by {\it italic\/} type. Any terms not explicitly
+defined in this document should be interpreted according to ISO 9899-1990,
+clause 3. Failing that, they should be interpreted according to other works
+listed in section \ref{sec:references}.
+\nobreak
+\defsubsection{implementation}: A library and set of C language headers
+which conforms to the specifications of this Document.
+\index{production mode}
+\indexmacro{NDEBUG}
+\defsubsection{production mode}: A mode of operating the implementation
+in such a way that maximum efficiency of execution is achieved at the expense
+of the verification of constraints. An implementation shall provide
+a production mode, which is enabled in an implementation-defined
+manner.\footnote{An implementation may have to supply a separate set of
+libraries for production and for verification use, for instance. The
+manner of selecting libraries varies with each programming environment.}  Each
+translation unit of the program which includes a Kazlib header shall ensure that the macro {\tt
+NDEBUG} is defined prior to the inclusion of that header, otherwise the
+implementation is not said to be operated in production mode.
+\index{verification mode}
+\defsubsection{verification mode}: A mode of operating the implementation in
+such a way that maximum error checking is obtained at the cost of
+execution efficiency. An implementation shall provide a verification mode, which
+is enabled in an implementation-defined manner. If any translation unit which
+includes a Kazlib header defines the macro name {\tt NDEBUG}\footnote{The
+intent is that the standard {\tt assert} macro may be exploited
+by the implementation's headers for the purpose of provisioning verification
+mode.} prior to including that header, the implementation is not said to be in
+verification mode. The least requirements of a Kazlib implementation operated
+in verification mode, is that it shall stop translation or execution of any
+program which violates a constraint. 
+\index{undefined behavior}
+\defsubsection{undefined behavior}: Behavior of a program, upon violation of a
+requirement with respect to the use of Kazlib, or upon use of corrupt or
+incorrect data, for which this document does not impose any requirements.
+Additional undefined behaviors are:
+\begin{itemize}
+\item any behavior that is undefined by the C language standard;
+\item evaluation of an object whose contents are indeterminate;
+\item a violation of any explicit constraint stated in
+this document, if that program was built using Kazlib in production
+mode;\footnote{The intent is that violations of constraints are diagnosed by
+the implementation in verification mode, and hence do not lead to undefined
+behavior.}
+\item a violation of any requirement stated in this document that
+is not designated as a constraint, and is introduced using the word
+{\it shall}; and
+\item any other construct for which no definition of behavior can be deduced
+from this document.
+\end{itemize}
+If a program invokes undefined behavior of any kind, the Kazlib implementation
+is absolved from any requirements as to what events should ensue.  The
+implementation may respond by invoking undefined behavior in the C language
+sense, or it may detect the behavior and terminate with a diagnostic message.
+\defsubsection{implementation-defined}: An adjective which, when appearing
+in the description of a feature, represents a requirement that the
+implementor must supply a definition, and document that
+definition. This adjective is applied to both behavior and to results.
+Implementation-defined behavior is behavior which depends on the
+characteristics of an implementation.\footnote{It is not considered adequate
+for the implementor to allow implementation-defined behavior to produce
+unpredictable effects or to terminate the program when such behavior is
+invoked.}  When said of a result,
+implementation-defined means that a value is successfully computed, but depends
+on the characteristics of the implementation. It is possible for the presence of a
+requirement on a program to be described as implementation-defined, giving the
+implementor a choice whether to make that requirement or not. If a program
+violates a requirement whose presence is implementation-defined, that program's
+behavior is undefined in any implementation which elects to in fact impose that
+requirement.
+\index{implementation-defined}
+\defsubsection{unpredictable result}: A successfully computed value which is
+unreliable because some procedure or data failed to satisfy a property required
+by the computation.
+\defsubsection{constraint}: A semantic restriction with which a program must
+comply. Some sections of this Document contain paragraphs under the heading
+{\it Constraints\/} which list all constraints pertaining to the described
+feature. When operated in production mode, the Kazlib implementation
+is not required to diagnose constraint violations. When operated in
+verification mode, the Kazlib implementation must halt translation or
+execution of a program which violates a constraint.
+\index{constraint}
+\defsubsection{comparison function}: A function which accepts two arguments
+\index{comparison function}
+of type \verb|const void *| and returns a value of type int based on
+a ranking comparison of these arguments, and which satisfies the following
+additional semantic properties. If the two arguments are deemed to be equal, the
+function must return zero. If the first argument is determined to have a
+greater rank than the second, a positive value is returned. Otherwise if the
+first argument is determined to have a lesser rank than the second, a negative
+value is returned. The rank is computed as if each value has associated with it
+an integer, not necessarily unique, and as if these integers are compared for ordinary equality or
+inequality when values are said to be compared.  The assignment of integers is
+up to the designer of the comparison function, and does not change between
+successive invocations of the function.\footnote{Of course, an actual
+comparison function need not assign actual integer ranks to data items, but it
+must behave as if such ranks were assigned.}
+If a comparison function is invoked in the context of an operation on some data
+structure, it shall not invoke any operation on any component of that same
+structure.\footnote{Thus, if a comparison function is invoked from, for
+instance, {\tt list_sort}, it must not call any list operations that 
+inspect or modify the list being sorted, or any of its constituent nodes.}
+\defsubsection{opaque data type}: A data type whose precise definition is
+not documented, and which is intended to be manipulated only using the
+documented interface, which consists of a set of functions.  Many data types in
+Kazlib are described as opaque. A program which bypasses the documented
+interfaces in inspecting or manipulating these data types invokes undefined
+behavior, and is not portable among Kazlib implementations.
+\defsubsection{user}: \index{user} The program which uses Kazlib.
+\defsubsection{user data}: \index{user data} Data provided by the program
+to which Kazlib stores a pointer, but otherwise does not inspect or modify.
+
+\section{Environment}
+\label{sec:environment}
+
+The translation and use of Kazlib requires a conforming, hosted implementation
+of the C language which meets the following additional minimal requirements:
+\begin{enumerate}
+\item The C implementation distinguishes external names by at least their
+initial 15 characters\footnote{The ISO 9899:1990 standard demands only that
+external names be distinguished by their initial six characters.}. External
+names that are distinct in their first 15 characters are treated by the
+implementation as distinct names.  Upper and lower case letters in external
+identifiers need not be treated as distinct.
+\item The C implementation does not claim the identifier \verb|__cplusplus|
+for its internal use as a preprocessor symbol or keyword.
+\end{enumerate}
+If Kazlib headers are used by a C++ program, the C++ implementation
+meets these additional requirements:
+\begin{enumerate}
+\item the C++ implementation identifies itself by predefining the preprocessor
+symbol \verb|__cplusplus|;
+\item the C++ implementation is be capable of linkage against
+the C implementation with which the Kazlib source files units were translated.
+\end{enumerate}
+The Kazlib headers shall not make use of any names that are claimed
+by the C++ programming language, and shall ensure that the \verb|extern "C"|
+mechanism is used for all declarations when they are included into a C++
+translation unit, or otherwise provide compatibility with C++.\footnote{The
+intent is that the Kazlib implementation could, in principle, provide 
+a separate set of headers for use with each language.}
+
+In programming environments that support the programming mechanism of multiple
+threads of execution an implementation of Kazlib may be designated as {\it
+thread safe}.  To be called thread safe, it must guarantee that the use of an
+object by one thread cannot visibly interact or interfere with the concurrent
+or interleaved use of another object by another thread. If a Kazlib
+implementation that is not thread safe is provided for an environment which
+supports threads, it shall be accompanied by documentation which describes
+the extent of this limitation.
+
+A Kazlib implementation can also be designated as being {\it async safe}.
+The minimum requirement for this designation is that an operation on an object
+can be interrupted by delivery of an asynchronous signal and from within the
+catching function for that signal, it is safe to perform an operation on
+another object.  An implementation shall document that it is async safe,
+or the extent to which it fails to be async safe.
+
+\section{General restrictions}
+
+\subsection{Headers}
+
+The Kazlib headers may be included in any order, and may be included more than
+once. Prior to the inclusion of a Kazlib header, the translation unit shall not
+define any macro name that has the same spelling as a C language keyword. The
+Kazlib headers may behave as though they include arbitrary standard C headers,
+so any requirements related to the inclusion of standard headers apply to
+Kazlib headers.  A header shall be included before the first reference to any
+of the functions, types or macros that it defines.
+
+If one or more preprocessor symbols whose names begin with the sequence
+\verb|KAZLIB_| are defined prior to the inclusion of a Kazlib header,
+the behavior is implementation-defined.
+
+\subsection{Reserved macros}
+
+A Kazlib header defines all of the macros explicitly listed in the section of
+this document that defines the contents of that header. It may also define
+additional macros that belong to the macro namespace reserved by that header.
+The translation unit that includes the header shall not \verb|#define| or
+\verb|#undef| any of these macros.
+
+A header may define function-like macros that supplement existing functions,
+provided that such macros do not cause multiple evaluation of arguments except
+as explicitly permitted, and are safe to use wherever the corresponding
+function call would be. These function-like macros may be subject to
+\verb|#undef|.\footnote{In principle, an implementation may provide, within the
+reserved namespaces, additional functions not specified in this document, and
+function-like macro equivalents of these functions. A program that uses such
+identifiers in a block or function scope should use {\tt \#undef} on these
+identifiers prior to their use.}
+
+\subsection{Reserved symbols}
+
+Each Kazlib header provides file scope declarations for the typedef names,
+struct tags, enum constants and function names listed in its corresponding
+section in this document. Moreover, each header may define additional such
+names that fall into the documented reserved namespaces.
+
+The behavior is undefined if a translation unit that includes a Kazlib header
+defines any identifier that is the same as an identifier reserved by the header
+in the same scope and namespace.\footnote{Therefore, it is permitted to redeclare
+or redefine the identifiers reserved by a previously included Kazlib header,
+provided that the declarations or definitions are in a different namespace or
+scope. Reserved names may be redeclared in a block scope, or used as
+statement labels which have function scope and are in their own namespace.}
+
+The behavior is also undefined if the program contains a definition of an
+object or function with external linkage whose name matches an external object
+of unction defined  by Kazlib component that is used as part of the
+program, or whose name is in a namespace reserved by that
+component.\footnote{This restriction exists whether or not the corresponding Kazlib
+header is included.} 
+
+Lastly, the behavior is undefined if a translation unit defines a macro whose
+name is in the space of reserved symbols of a Kazlib header that is included in
+that translation unit.
+
+\subsection{Argument aliasing}
+
+Kazlib provides functions that operate on objects of various types.  Pointers
+to objects are passed to these functions, thereby giving rise to the
+possibility of {\it aliasing}---passing of objects that wholly or partially
+overlap.  The program shall not present aliased objects to any Kazlib function.
+Objects of distinct types shall not be aliased in a function call under any
+circumstances.
+The aliasing of two or more objects of compatible type is permitted only as
+explicitly documented in the description of a function; in all such
+circumstances, only exact overlapping is permitted.\footnote{That is to say,
+where explicitly allowed, a pointer to the same object may be specified for two
+(or more) parameters of like type.}
+
+\subsection{Object initialization}
+
+The Kazlib opaque data types can only be initialized with the initialization
+functions provided by the Kazlib library, or by implementation-defined
+initialization functions.\footnote{Of course, the use of implementation-defined
+functions results in programs that are not portable among library
+implementations.} An opaque object that is initialized by a method other than
+by being passed to an appropriate initialization function, or that is not
+initialized at all, has indeterminate contents.  A pointer to an object having
+indeterminate contents may be passed to an initialization function; the object
+then has well-determined contents.
+
+An object whose initialization function is capable of indicating failure is
+considered indeterminate if the attempt to initialize that object using that
+function does in fact fail. The program shall not attempt to deinitialize such
+an object. The implementation shall reclaim any resources that were allocated
+for an object whose initialization failed. This reclamation need
+not be immediate, but may be delayed; however, the delay shall not
+give rise to the possibility of resource leaks in any correct program.
+
+Those objects for which deinitialization operations are defined should be
+subject to these operations when these objects are no longer needed.  Failure
+to apply the deinitialization functions may result in the leakage of resources.
+
+\subsection{Object copying}
+
+Certain data types may be sensitive to their own location in memory.  This
+means that copying their values by assignment or \verb|memcpy| results in the
+copy having an indeterminate value which cannot be used.  All opaque types in
+Kazlib are assumed to have this property; copying the value of an opaquely
+typed object to another suitably typed object causes the destination
+object to have indeterminate contents. 
+
+\section{List component}
+
+The List component provides a set of functions, macros and type declarations
+which together provide a library for maintaining a possibly empty ordered set
+of elements, called a {\it list}. This list has the following properties:
+\index{List}\begin{enumerate}
+\item If the list is not empty, a first and last element can be identified.
+    In a list having only one element, that one element is both the first and
+    last element.
+\item Each element that is not the last element has another element as its
+    {\it successor}.
+    \index{successor!of a list element}
+    \index{List!successor of an element}
+\item Each element that is not the first element has a {\it
+    predecessor}.
+    \index{predecessor!of a list element}
+    \index{List!predecessor of an element}
+\item No element is the predecessor or successor of more than one element.
+\item If one element is the successor of another, the other is necessarily the
+    predecessor of the first.
+\item Each element is associated with arbitrary {\it satellite\/} data.
+\end{enumerate}
+The {\it size} of a list, also known as the {\it list count}, is simply the
+number of elements contained in it.\index{size!of a list}\index{List!count}
+
+A list imposes a maximum value on the number of nodes that may be in it
+simultaneously. This is known as the list's {\it capacity}. A list that
+has the maximum number of nodes is said to be full.
+
+\subsection{Interface}
+
+\subsubsection{The {\tt list.h} header}
+
+Each C or C++ translation unit that is to use the functionality of
+the List component shall include the header \verb|list.h|. This header
+shall contain declarations of types and external functions, and definitions of
+macros.
+The following typedef names shall be defined:\index{List!typedef names}
+\index{typedefs!defined by List}
+\begin{verbatim}
+    list_t                      listcount_t
+    lnode_t                     lnodepool_t
+\end{verbatim}
+In addition, the following structure tags may be defined:\index{List!tag names}
+\index{tags!defined by List}
+\begin{verbatim}
+    struct list_t
+    struct lnode_t
+    struct lnodepool_t
+\end{verbatim}
+The following external function names shall be declared:
+\index{List!function names}\index{functions!defined by List}
+\begin{verbatim}
+    list_append                 list_prev                          
+    list_contains               list_process                       
+    list_count                  list_return_nodes                  
+    list_create                 list_sort                          
+    list_del_first              list_find
+    list_del_last               list_transfer                      
+    list_delete                 list_verify                        
+    list_destroy                lnode_borrow                       
+    list_destroy_nodes          lnode_create                       
+    list_extract                lnode_destroy                      
+    list_first                  lnode_get                          
+    list_init                   lnode_init                         
+    list_ins_after              lnode_is_in_a_list                 
+    list_ins_before             lnode_pool_create                  
+    list_is_sorted              lnode_pool_destroy                 
+    list_isempty                lnode_pool_init                    
+    list_isfull                 lnode_pool_isempty                 
+    list_last                   lnode_pool_isfrom                  
+    list_merge                  lnode_put                          
+    list_next                   lnode_return                       
+    list_prepend                
+\end{verbatim}
+The following preprocessor symbols (macros) shall be defined:
+\index{List!macro names}\index{macros!defined by List}
+\indexmacro{LISTCOUNT_T_MAX}
+\indexmacro{LIST_H}
+\begin{verbatim}
+    LISTCOUNT_T_MAX
+    LIST_H\end{verbatim}
+\index{symbols!reserved by List}\index{List!reserved symbols}
+Macro identifiers which begin with the upper-case prefix \verb|LIST| are
+reserved for future extensions to the \verb|list.h| header, as are
+names in the ordinary and tag namespaces which begin with
+\verb|list_| or \verb|lnode_|. External names which begin with \verb|list_| or
+\verb|lnode_| are reserved by the Kazlib library regardless of what header
+files are included.
+
+\subsubsection{The {\tt list_t} type}
+
+\indextype{list_t}
+The type \verb|list_t| is an opaque data type which maintains information about the
+current state of a single list.  A list consists of an instance of the
+\verb|list_t| type, plus zero or more instances of the type \verb|lnode_t|. An
+instance of the \verb|list_t| type can be dynamically created using the
+\verb|list_create| function, and destroyed by the \verb|list_destroy| function.
+Alternately, the program can declare an object of type \verb|list_t| and have
+it initialized via the \verb|list_init| function. 
+
+\subsubsection{The {\tt listcount_t} type}
+
+\indextype{listcount_t}
+\indexmacro{LISTCOUNT_T_MAX}
+The type \verb|listcount_t| is an unsigned integral type which represents
+the number of nodes in a list. The specific choice of unsigned integral type
+is implementation defined. The \verb|LISTCOUNT_T_MAX| macro expands to a
+constant expression of type \verb|listcount_t| which specifies the maximum
+value of that type.\footnote{For example, if the implementation defines
+{\tt listcount_t} as an alias for the type unsigned long, then 
+{\tt LISTCOUNT_T_MAX} must have the same value as {\tt ULONG_MAX}.}
+
+\subsubsection{The {\tt lnode_t} type}
+
+\indextype{lnode_t}
+The type \verb|lnode_t| is an opaque type that represents a single node of a
+list. A node contains a a reference to satellite data provided by the user,
+and also stores the key that is associated with the node when it is inserted.
+Nodes may be dynamically created by the \verb|lnode_create| function.
+Alternately, the program may supply an \verb|lnode_t| object that can be
+initialized by the \verb|lnode_init| function. 
+
+\subsubsection{The {\tt lnodepool_t} type}
+
+\indextype{lnodepool_t}
+The \verb|lnodepool_t| type provides an alternate method for supplying list
+nodes to the application. A user-supplied or dynamically allocated fixed size
+array of nodes is converted into a a {\it pool\/} of nodes from which free
+nodes may be obtained and to which they may be returned. A user-supplied node
+pool is created by the function \verb|lnode_pool_init| which requires a pointer
+to an object of type \verb|lnode_pool_t|, a pointer to the first element of an
+array of \verb|lnode_t| objects, as well as an integer representing the size of
+the array. Alternately, the function \verb|lnode_pool_create| will dynamically
+allocate an object of type \verb|lnode_pool_t| containing the specified number
+of list nodes. 
+
+\subsubsection{The {\tt list_append} function}
+
+    \indexfunc{list_append}
+    \index{List!appending a node}
+    \index{append node to list}
+    \synopsis
+    \begin{verbatim}
+    void list_append(list_t *, lnode_t *);\end{verbatim}
+
+    \constraints
+    The second argument shall not refer to a node that is already in a list
+    or in a list node pool. The first argument shall not refer to a list
+    that is full.
+
+    \description
+    The append operation causes the node pointed at by the second
+    argument to become the last node in the list pointed at by the first
+    argument.\footnote{That is to say, after the operation, the
+    {\tt list_last} function, when applied to the list, shall return a pointer
+    to that node.}
+
+    If the first argument is an expression with side effects, the behavior
+    is undefined.\footnote{Thus, the implementation may provide a macro
+    version of {\tt list_append} which evaluates the first argument
+    more than once.}
+    \index{macros!and side effects}
+
+\subsubsection{The {\tt list_contains} function}
+
+    \indexfunc{list_contains}
+    \index{List!testing for presence of node}
+    \nobreak
+    \synopsis
+    \begin{verbatim}
+    int list_contains(list_t *, lnode_t *node);\end{verbatim}
+    \nobreak
+    \description
+    \nobreak
+    The \verb|list_contains| function shall return 1 if the node
+    pointed at by the second argument is in the list pointed at by the first
+    argument.  Otherwise, it shall return 0.
+
+\subsubsection{The {\tt list_count} function}
+
+    \indexfunc{list_count}
+    \index{List!count}
+    \index{List!size}
+    \synopsis
+    \begin{verbatim}
+    listcount_t list_count(list_t *);\end{verbatim}
+
+    \description
+
+    The \verb|list_count| function returns a value which represents the number
+    of nodes currently stored in the list pointed at by the argument.
+
+\subsubsection{The {\tt list_create} function}
+
+    \indexfunc{list_create}
+    \index{List!creation of}
+    \index{create!list object}
+    \synopsis
+    \begin{verbatim}
+    list_t *list_create(listcount_t);\end{verbatim}
+
+    \description
+    The \verb|list_create| function instantiates and initializes an object of
+    type \verb|list_t|, and returns a pointer to it unless insufficient
+    resources exist for the creation of the object, in which case a null
+    pointer is returned.
+
+    The value of the function's argument establishes, for the entire duration
+    of the list object, its capacity.
+
+    The newly created list object is empty.
+
+\subsubsection{The {\tt list_del_first} function}
+
+    \index{List!first node}
+    \indexfunc{list_del_first}
+    \index{List!deletion}
+    \index{delete!first node of a list}
+    \synopsis
+    \begin{verbatim}
+    lnode_t *list_del_first(list_t *);\end{verbatim}
+
+    \constraints
+    The argument shall not point to an empty list.
+
+    \description
+    The \verb|list_del_first| function removes the first node from the
+    list pointed at by the argument and returns a pointer to that
+    node. 
+
+    If the argument is an expression with side effects, the behavior is
+    undefined.\index{macros!and side effects}
+
+\subsubsection{The {\tt list_del_last} function}
+
+    \index{List!last node}
+    \indexfunc{list_del_last}
+    \index{List!deletion}
+    \index{delete!last node of a list}
+    \synopsis
+    \begin{verbatim}
+    lnode_t *list_del_last(list_t *);\end{verbatim}
+
+    \constraints
+    The argument shall not point to an empty list.
+
+    \description
+    The \verb|list_del_last| function removes the last node from the list
+    specified by the argument, and returns a pointer to that node. If,
+    prior to the operation, that node had a predecessor, that predecessor
+    shall become the new last node of the list. Otherwise, the list
+    shall become empty.
+
+    The new value of the list count shall be one less than its value
+    prior to the call to this function.
+
+    If the argument is an expression with side effects, the behavior is
+    undefined.\index{macros!and side effects}
+
+\subsubsection{The {\tt list_delete} function}
+
+    \indexfunc{list_delete}
+    \index{List!deletion}
+    \index{delete!arbitrary node of a list}
+    \synopsis
+    \begin{verbatim}
+    lnode_t *list_delete(list_t *, lnode_t *);\end{verbatim}
+
+    \constraints
+    The second argument shall point to a node that is inside the list
+    pointed at by the first argument.
+
+    \description
+    The \verb|list_delete| function removes the node pointed at by its
+    second argument from the list pointed at by its first argument.
+    A pointer to the deleted node is returned.
+
+\subsubsection{The {\tt list_destroy} function}
+
+    \indexfunc{list_destroy}
+    \index{List!destruction of}
+    \synopsis
+    \begin{verbatim}
+    void list_destroy(list_t *);\end{verbatim}
+
+    \constraints
+    The argument shall point to an empty list.
+
+    \description
+    The empty list pointed at by the argument is destroyed. If the list has
+    not been created by a call to the \verb|list_create| function, the
+    behavior is undefined.
+
+    A pointer that previously referred to a list that has been disposed by
+    \verb|list_destroy| has an indeterminate value.
+
+\subsubsection{The {\tt list_destroy_nodes} function}
+
+    \indexfunc{list_destroy_nodes}
+    \synopsis
+    \begin{verbatim}
+    void list_destroy_nodes(list_t *);\end{verbatim}
+
+    \description
+    The nodes, if any, contained in the list pointed at by the argument are
+    disposed of as if by a call to the \verb|lnode_destroy| function. If any
+    node contained in the list was created by means other than the
+    \verb|lnode_create| function, the behavior is undefined.
+
+    After the operation, the list is empty.
+
+    Any pointer that referred to any of the destroyed nodes takes on an
+    indeterminate value.
+
+\subsubsection{The {\tt list_extract} function}
+
+    \index{List!node range extraction}
+    \indexfunc{list_extract}
+    \synopsis
+    \begin{verbatim}
+    void list_extract(list_t *, list_t *, lnode_t *, lnode_t *);\end{verbatim}
+
+    \constraints
+    The second argument points to the {\it source list}. The third
+    argument is either null, or points to a node that is an occupant
+    of the source list. This node is called the {\it starting node}.
+    The fourth argument is either null, or points to a node that is
+    an occupant of the source list. This node is called the {\it ending
+    node}. If the starting node and ending node are both specified, and are
+    distinct nodes, then the starting node shall appear earlier in the source
+    list than the ending node.
+
+    The transfer request shall not call for the capacity of the  destination
+    list to be exceeded.
+
+    \description
+    The \verb|list_extract| function moves nodes from the source
+    list to the {\it destination list\/} pointed at by the first
+    argument.\footnote{This right-to-left direction of transfer is consistent
+    with the semantics of standard C library functions such as {\tt memmove} or
+    {\tt strcpy}.}
+
+    If the third and fourth arguments are not null, the entire range of nodes
+    from the starting node and to the ending node, inclusive, is transferred
+    from the source list to the end of the destination list, where they appear
+    in their original order. Other nodes in the source list, if any, are
+    unaffected.
+
+    If the third and fourth arguments both point to the same node, that
+    node alone is transferred to the end of the destination list.
+
+    If either the third argument or the fourth argument is null, or both are null,
+    no transfer of nodes takes place.
+
+    The source and destination list may be the same object.
+
+\subsubsection{The {\tt list_first} function}
+
+    \index{List!first node}
+    \indexfunc{list_first}
+    \synopsis
+    \begin{verbatim}
+    lnode_t *list_first(list_t *);\end{verbatim}
+
+    \description
+    If the list pointed at by the argument is an empty list, a null pointer
+    is returned. Otherwise, a pointer to the first node in that list is
+    returned.
+
+    If the argument is an expression with side effects, the behavior is
+    undefined.\index{macros!and side effects}
+
+\subsubsection{The {\tt list_init} function}
+
+    \indexfunc{list_init}
+    \synopsis
+    \begin{verbatim}
+    list_t *list_init(list_t *, listcount_t);\end{verbatim}
+
+    \constraints
+    The second argument shall not have a zero value.
+
+    \description
+    The \verb|list_init| function initializes the list object pointed at by the
+    first argument, turning it into a valid, empty list. If the object is an
+    already initialized list, the behavior is undefined. A list returned by
+    \verb|list_create| is considered initialized. The second argument 
+    specifies the maximum number of nodes that may simultaneously occupy the
+    list.
+
+    The value returned is that of the first argument.
+
+\subsubsection{The {\tt list_ins_after} function}
+
+    \indexfunc{list_ins_after}
+    \index{insert!node into list}
+    \index{List!insertion}
+    \synopsis
+    \begin{verbatim}
+    void list_ins_after(list_t *, lnode_t *, lnode_t *);\end{verbatim}
+
+    \constraints
+    The first argument shall point to a list that is not already full.  The
+    second argument shall point to a node, called the {\it new node}, that is not
+    already an occupant of the list pointed at by the first argument, nor
+    of any other list or node pool object. The third
+    argument shall point to a node, called the {\it reference node}, that is an
+    occupant of the list.
+
+    \description
+    The new node becomes an occupant of the list, such that its predecessor
+    is the reference node. If the reference node has a successor, the
+    new node is inserted between the reference node and that successor.
+    Otherwise, the new node becomes the last node of the list.
+
+\subsubsection{The {\tt list_ins_before} function}
+
+    \indexfunc{list_ins_before}
+    \index{insert!node into list}
+    \index{List!insertion}
+    \synopsis
+    \begin{verbatim}
+    void list_ins_before(list_t *, lnode_t *, lnode_t *);\end{verbatim}
+
+    \constraints
+    The first argument shall point to a list that is not already full.  The
+    second argument shall point to a node, called the {\it new node}, that is not
+    already an occupant of the list pointed at by the first argument, nor
+    of any other list or node pool object. The third
+    argument shall point to a node, called the {\it reference node}, that is an
+    occupant of the list.
+
+    \description
+    The new node becomes an occupant of the list, such that its successor
+    is the reference node. If the reference node has a predecessor, the
+    new node is inserted between the reference node and that predecessor.
+    Otherwise, the new node becomes the first node of the list.
+    
+\subsubsection{The {\tt list_is_sorted} function}
+\label{list:is:sorted}
+    \indexfunc{list_is_sorted}
+
+    \synopsis
+    \begin{verbatim}
+    int list_is_sorted(list_t *,
+            int (const void *, const void *));\end{verbatim}
+
+    \description
+    The first argument points to a list object. The second is assumed to
+    point to a comparison function.
+    
+    If the list has exactly one node or is empty, $1$ is returned
+    unconditionally.  Otherwise, nodes of the list are examined to
+    determine whether they are in a sorted order according to the comparison
+    function. This is true if the integer ranks of their data items,
+    examined from the first node of the list through to the last node, form a
+    monotonically increasing sequence. If the nodes are in order, the value $1$
+    is returned. Otherwise $0$ is returned.
+    
+    If the list has two or more nodes, and the second argument is a pointer to
+    a function that has the correct type, but does not satisfy the semantic
+    properties of a comparison function, the result is unpredictable, but is
+    guaranteed to be one of the values~$0$~or~$1$. 
+
+\subsubsection{The {\tt list_isempty} function}
+
+    \indexfunc{list_isempty}
+    \synopsis
+    \begin{verbatim}
+    int list_isempty(list_t *);\end{verbatim}
+
+    \description
+    The \verb|list_isempty| function returns $1$ if the list pointed at by
+    the first argument is empty. Otherwise it returns $0$.
+
+\subsubsection{The {\tt list_isfull} function}
+
+    \indexfunc{list_isfull}
+    \synopsis
+    \begin{verbatim}
+    int list_isfull(list_t *);\end{verbatim}
+
+    \description
+    The \verb|list_isfull| function returns $1$ if the list pointed at by
+    the first argument is full. Otherwise it returns $0$.
+    A list is considered full when it contains the maximum number of nodes
+    that was specified upon its initialization.
+
+    If the argument is an expression with side effects, the behavior is
+    undefined.\index{macros!and side effects}
+
+\subsubsection{The {\tt list_last} function}
+
+    \index{List!last node}
+    \indexfunc{list_last}
+    \synopsis
+    \begin{verbatim}
+    lnode_t *list_last(list_t *);\end{verbatim}
+
+    \description
+    If the list pointed at by its first argument is empty, the \verb|list_last|
+    function returns a null pointer. Otherwise it returns a pointer to the
+    last node.
+
+    If the argument is an expression with side effects, the behavior is
+    undefined.\index{macros!and side effects}
+
+\subsubsection{The {\tt list_merge} function}
+
+    \index{List!merge operation}
+    \indexfunc{list_merge}
+    \synopsis
+    \begin{verbatim}
+    void list_merge(list_t *, list_t *,
+            int (const void *, const void *));\end{verbatim}
+
+    \constraints
+    The list pointed at by the first argument is called the {\it destination
+    list}. The second argument points to the {\it source list}. The third
+    argument points to a comparison function.  The sum of the number of nodes
+    occupying the source list and the destination list shall not exceed the
+    maximum number of nodes that are permitted to occupy the destination list.
+    Furthermore, both the source and destination list shall be sorted such that
+    a call to \verb|list_is_sorted| given a pointer to either list as a first
+    argument, and the pointer to the comparison function as its second
+    argument, shall yield the value $1$.
+
+    \description
+    Nodes from the sorted source list are merged into the sorted destination
+    list. After the operation, the source list is empty and the destination
+    list contains all of the nodes it contained prior to the operation, as well
+    as all of the nodes that the source list contained. The nodes are in sorted
+    order according to the comparison function.
+
+    If the third argument is a pointer to a function that has the correct type,
+    but does not fulfill the semantic properties of a comparison function, the
+    order of the nodes in the destination list is unpredictable.
+
+    If the source and destination list are the same object, the
+    \verb|list_merge| operation has no effect.
+
+\subsubsection{The {\tt list_next} function}
+
+    \indexfunc{list_next}
+    \synopsis
+    \begin{verbatim}
+    lnode_t *list_next(list_t *, lnode_t *);\end{verbatim}
+
+    \constraints
+    The node pointed at by the second argument is an occupant of the list pointed
+    at by the first argument.
+
+    \description
+    If the node pointed at by the second argument has a successor, a pointer to
+    that successor is returned. Otherwise, a null pointer is returned.
+
+    If the second argument is an expression which has side effects, the behavior
+    is undefined.\index{macros!and side effects}
+
+\subsubsection{The {\tt list_prepend} function}
+
+    \indexfunc{list_prepend}
+    \index{List!prepending a node}
+    \index{prepend node to list}
+    \synopsis
+    \begin{verbatim}
+    void list_prepend(list_t *, lnode_t *);\end{verbatim}
+
+    \constraints
+    The second argument shall not refer to a node that is already in a list
+    or in a list node pool. The first argument shall not refer to a list
+    that is full.
+
+    \description
+    The prepend operation causes the node pointed at by the second
+    argument to become the first node in the list pointed at by the first
+    argument. After the operation, the \verb|list_first| function, when
+    applied to the list, shall return a pointer to that node.
+    If, prior to to the operation, the list is empty, then the prepended node
+    shall become the first node in that list, otherwise, the prepended node
+    becomes the predecessor of what was previously the first node.
+
+    If the first argument is an expression with side effects, the behavior
+    is undefined.\index{macros!and side effects}
+
+\subsubsection{The {\tt list_prev} function}
+
+    \indexfunc{list_prev}
+    \synopsis
+    \begin{verbatim}
+    lnode_t *list_prev(list_t *, lnode_t *);\end{verbatim}
+
+    \constraints
+    The node pointed at by the second argument is an occupant of the list pointed
+    at by the first argument.
+
+    \description
+    If the node pointed at by the second argument has a predecessor, a pointer to
+    that predecessor is returned. Otherwise, a null pointer is returned.
+
+    If the second argument is an expression which has side effects, the behavior
+    \index{macros!and side effects}
+    is undefined.
+
+\subsubsection{The {\tt list_process} function}
+
+    \indexfunc{list_process}
+    \synopsis
+    \begin{verbatim}
+    void list_process(list_t *, void *,
+            void (*)(list_t *, lnode_t *, void *));\end{verbatim}
+    \nobreak
+    \description
+    The \verb|list_process| function iterates over the nodes of a list,
+    and for each node invokes a callback function.\footnote{In most cases,
+    it is more convenient and preferable to 
+    iterate over the list using explicit calls to {\tt list_first}
+    and {\tt list_next}.}
+    The second argument is a {\it context pointer\/} which can have any value.
+    The third argument of
+    \verb|list_process| shall be a pointer to a function which is compatible
+    with the specified type. If the list contains one or more nodes,
+    then the function is invoked once for each node, in order from first
+    to last. On each invocation, the first argument of the callback is a
+    pointer to the list; the second argument is a pointer to a node, called
+    the {\it subject node}; and the third argument repeats the context pointer
+    value that was originally passed to \verb|list_process|.
+
+    The callback function may delete the subject node by, for instance, calling
+    \verb|list_delete|. It may insert new nodes to any place in the list;
+    however, if such an insertion causes the subject node to acquire
+    a new successor, it is implementation-defined whether upon returning
+    from the callback function, the traversal shall continue with the
+    new successor, or with the original successor.
+    
+    The callback function, and any function invoked from the callback
+    function, shall not destroy the list or make any modifications
+    other than the insertion of new nodes, or the deletion of the 
+    subject node.
+
+    The callback function may recursively invoke \verb|list_process| for the
+    same list or for a different list; the callback invocations arising out of
+    the nested call inherit all of the restrictions of the outer callback in
+    addition to being subject to the usual restrictions.\footnote{This means,
+    for instance, that if two callbacks are in progress for different
+    subject nodes from the same list, the inner callback may not delete
+    its subject node, because it inherits the restriction that the only
+    permitted deletion is the outer callback's subject node.}
+
+    The callback function may freely operate on a different list,
+    subject to any inherited restrictions.
+
+\subsubsection{The {\tt list_return_nodes} function}
+
+    \indexfunc{list_return_nodes}
+    \synopsis
+    \begin{verbatim}
+    void list_return_nodes(list_t *, lnodepool_t *);\end{verbatim}
+
+    \description
+
+    Every node in the list specified by the first argument
+    is returned to the node pool specified by the second argument
+    If the list contains a node that has not been allocated
+    from that node pool, the behavior is undefined.
+
+\subsubsection{The {\tt list_sort} function}
+
+    \index{List!sort operation}
+    \indexfunc{list_sort}
+    \synopsis
+    \begin{verbatim}
+    void list_sort(list_t *, int (const void *, const void *));\end{verbatim}
+
+    \description
+
+    The \verb|list_sort| function changes the order of the nodes of the list
+    specified by the first argument according to the comparison function
+    pointed at by the second argument. 
+
+    If the list is empty, or contains only one node, the comparison function is
+    not called.
+
+    Whenever the comparison function is invoked, its arguments are are the data
+    pointers stored in two distinct nodes of the list.
+
+\subsubsection{The {\tt list_find} function}
+
+    \index{List!find operation}
+    \indexfunc{list_find}
+    \synopsis
+    \begin{verbatim}
+    lnode_t *list_find(list_t *,
+           const void *, int (const void *, const void *));\end{verbatim}
+
+    \description
+
+    The \verb|list_find| function exhaustively searches the key for a node
+    whose satellite data matches a search key according to the comparison
+    function. The first argument is the list to be searched, the second
+    argument specifies the search key and the third argument is a pointer
+    to the comparison function.
+
+    The comparison function is invoked to compare the key against the
+    satellite data of successive nodes of the list, starting with the first
+    node. A pointer to the first node for which the comparison function returns
+    zero is returned.
+
+    If the list is empty, or the comparison function returns non-zero for
+    each item, a null pointer is returned.
+
+\subsubsection{The {\tt list_transfer} function}
+
+    \index{List!node transfer}
+    \indexfunc{list_transfer}
+    \synopsis
+    \begin{verbatim}
+    void list_transfer(list_t *, list_t *, lnode_t *);\end{verbatim}
+
+    \constraints
+    The third argument is either null, or it points at a node which is an
+    occupant of the list pointed at by the second argument.
+
+    The transfer request shall not call for the capacity of the  destination
+    list to be exceeded.
+
+    \description
+    The \verb|list_transfer| function moves nodes from the list
+    pointed at by the second argument to the list pointed at by
+    the first argument.
+
+    If the third argument is not null, it specifies the node in the source list
+    at which the transfer begins. That node, its successor, and all 
+    subsequent nodes, are transferred to the end of the destination list where
+    they appear in their original order. Other nodes in the source list are
+    unaffected.
+
+    If the third argument is null, no transfer of nodes takes place.
+
+    The source and destination list may be the same object.
+
+    If \verb|DL|, \verb|SL| and \verb|SN| are appropriately typed expressions,
+    the function call
+
+\begin{verbatim}
+    void list_transfer(DL, SL, SN);
+\end{verbatim}
+    is equivalent to 
+\begin{verbatim}
+    list_extract(DL, SL, SN, list_last(SL));
+\end{verbatim}
+    except that \verb|SL| is evaluated only once.
+
+\subsubsection{The {\tt list_verify} function}
+
+    \indexfunc{list_verify}
+    \synopsis
+    \begin{verbatim}
+    int list_verify(list_t *list);\end{verbatim}
+
+    \description
+    The intent of the \verb|list_verify| function is to perform a verification
+    on the list object, regardless of whether the Kazlib implementation is
+    operated in verification or production mode. If the list objects
+    and its constituent nodes have been correctly manipulated, and the
+    program has not caused any undefined behaviors, the value $1$ is returned.
+    Otherwise, the function may be able to, but is not guaranteed to, detect
+    corruption, and return the value zero.
+
+\subsubsection{The {\tt lnode_borrow} function}
+
+    \indexfunc{lnode_borrow}
+    \synopsis
+    \begin{verbatim}
+    lnode_t *lnode_borrow(lnodepool_t *, void *);\end{verbatim}
+
+    \description
+
+    The \verb|lnode_borrow| function allocates a node from
+    the pool managed by the given \verb|lnodepool_t| object.
+    If the request succeeds, a pointer to the node is returned.  If the object
+    has run out of nodes, the return value is a null pointer.
+
+\subsubsection{The {\tt lnode_create} function}
+
+    \indexfunc{lnode_create}
+    \synopsis
+    \begin{verbatim}
+    lnode_t *lnode_create(void *);\end{verbatim}
+
+    \description
+
+    The \verb|lnode_create| function dynamically allocates a list node,
+    stores in it the data value specified in the argument and
+    returns a pointer to it. The allocation is performed by a call to the
+    standard \verb|malloc| function. If the allocation fails, a null
+    pointer is returned.
+
+\subsubsection{The {\tt lnode_destroy} function}
+
+    \indexfunc{lnode_destroy}
+    \synopsis
+    \begin{verbatim}
+    void lnode_destroy(lnode_t *);\end{verbatim}
+
+    \description
+
+    The \verb|lnode_destroy| function destroys a list node that has been
+    allocated with the \verb|lnode_create| function.  The value of any pointer
+    that referred to the node that was thus freed is indeterminate.
+
+    If the node is currently the occupant of a list, the behavior is undefined
+    if the list is subsequently used.
+
+\subsubsection{The {\tt lnode_get} function}
+
+    \indexfunc{lnode_get}
+    \synopsis
+    \begin{verbatim}
+    void *lnode_get(lnode_t *);\end{verbatim}
+
+    \description
+
+    The \verb|lnode_get| function retrieves the \verb|void *| data value
+    associated with a node.\footnote{This is the {\bf only} interface for
+    retrieving the data element.}
+    
+\subsubsection{The {\tt lnode_init} function}
+
+    \indexfunc{lnode_init}
+    \synopsis
+    \begin{verbatim}
+    lnode_t *lnode_init(lnode_t *, void *);\end{verbatim}
+
+    The \verb|lnode_init| function initializes the contents
+    of the specified list node object, assigning it the
+    data value specified as the second argument. 
+    The first argument is a pointer which refers to 
+    a data object that has a suitable size and alignment
+    for the representation of an \verb|lnode_t| type.
+    After initialization with \verb|lnode_init|, the object is subsequently
+    eligible as an operand to the functions of the List component.
+
+\subsubsection{The {\tt lnode_is_in_a_list} function}
+
+    \indexfunc{lnode_is_in_a_list}
+    \synopsis
+    \begin{verbatim}
+    int lnode_is_in_a_list(lnode_t *);\end{verbatim}
+
+    \description
+
+    The \verb|lnode_is_in_a_list| function determines whether the given node is
+    an occupant of some list. If the node is in a list, the function returns
+    the value $1$.  If the node is not in any list, the return value is zero.
+
+\subsubsection{The {\tt lnode_pool_create} function}
+
+    \indexfunc{lnode_pool_create}
+    \synopsis
+    \begin{verbatim}
+    lnodepool_t *lnode_pool_create(listcount_t);\end{verbatim}
+
+    \constraints
+
+    The value of the argument shall not be zero.
+
+    \description
+
+    The \verb|lnode_pool_create| function dynamically allocates,
+    by means of the standard library function \verb|malloc|
+    a node pool object containing the number of nodes specified
+    as the first argument. If not enough resources are available,
+    a null pointer is returned, otherwise a pointer to the
+    \verb|lnodepool_t| object is returned.
+
+\subsubsection{The {\tt lnode_pool_destroy} function}
+
+    \indexfunc{lnode_pool_destroy}
+    \synopsis
+    \begin{verbatim}
+    void lnode_pool_destroy(lnodepool_t *);\end{verbatim}
+
+    \description
+
+    The \verb|lnode_pool_destroy| function deallocates a
+    node pool that was allocated by \verb|lnode_pool_create|.
+    The value of any pointer which referred to the
+    node pool object becomes indeterminate.
+
+\subsubsection{The {\tt lnode_pool_init} function}
+
+    \indexfunc{lnode_pool_init}
+    \synopsis
+    \begin{verbatim}
+    lnodepool_t *lnode_pool_init(lnodepool_t *,
+            lnode_t *, listcount_t);\end{verbatim}
+
+    \constraints
+
+    The third argument, which specifies the node count, shall not be zero.
+
+    \description
+
+    The \verb|lnode_pool_init| function initializes a data object
+    that has a suitable size and alignment to represent an
+    \verb|lnodepool_t| type. A pointer to this object is passed
+    as the first argument. The node pool thus created draws nodes
+    from an array specified by the second argument, which shall be a pointer to
+    an object that can behave like an array of \verb|lnode_t| objects.
+    The third argument specifies the number of elements in this array.
+
+    After this function, the object pointed at by the \verb|lnodepool_t *|
+    argument is eligible for use with the node pool management functions
+    of the List component. Nodes may be drawn from the pool and returned to it.
+
+    As long as the pool continues to be used, the program should not directly
+    manipulate the node array. In particular, if the program modifies any
+    part of the array, then the behavior is undefined if the
+    \verb|lnodepool_t| object or any nodes drawn from it are subsequently
+    passed to a List function. The program shall not directly use the array
+    elements as independent \verb|lnode_t| objects while the array is
+    associated with the pool; in particular, it shall not pass these elements
+    to Kazlib functions that operate on \verb|lnode_t|.
+    
+    The behavior is undefined if the same array is associated with more than
+    one node pool object, or if two node pool objects are given overlapping
+    arrays.
+
+    The node array is managed in an manner that is specific to the
+    implementation; the intent is that each element of the array represents a
+    distinct node object, a pointer to which can be returned in response to an
+    allocation request.
+
+    The \verb|lnode_pool_init| function returns a copy of the first argument.
+
+\subsubsection{The {\tt lnode_pool_isempty} function}
+
+    \indexfunc{lnode_pool_isempty}
+    \synopsis
+    \begin{verbatim}
+    int lnode_pool_isempty(lnodepool_t *);\end{verbatim}
+
+    \description
+
+    The \verb|lnode_pool_isempty| function tests the 
+    specified \verb|lnodepool_t| object for ability to supply nodes.
+    If the object has been
+    subject to so many requests that it is no longer capable of
+    of supplying additional list nodes, the value $1$ is returned.
+    Otherwise the return value returned is zero.
+
+\subsubsection{The {\tt lnode_pool_isfrom} function}
+
+    \indexfunc{lnode_pool_isfrom}
+    \synopsis
+    \begin{verbatim}
+    int lnode_pool_isfrom(lnodepool_t *, lnode_t *);\end{verbatim}
+
+    \description
+
+    The function \verb|lnode_pool_isfrom|, intended to serve as a software
+    verification aid, determines whether a list node originates from
+    a particular node pool. The return value is $1$ if this relationship is
+    true, otherwise zero.
+
+\subsubsection{The {\tt lnode_put} function}
+
+    \indexfunc{lnode_put}
+    \synopsis
+    \begin{verbatim}
+    void lnode_put(lnode_t *, void *);\end{verbatim}
+
+    \description
+
+    The function \verb|lnode_put| replaces the data element
+    associated with the list node. 
+
+\subsubsection{The {\tt lnode_return} function}
+
+    \indexfunc{lnode_return}
+    \synopsis
+    \begin{verbatim}
+    void lnode_return(lnodepool_t *, lnode_t *);\end{verbatim}
+
+    \constraints
+
+    The node pointed at by the second argument was derived by an allocation
+    request from the pool pointed at by the first argument.\footnote{In
+    other words, the {\tt lnode_pool_isfrom} function, were it called with
+    the same two arguments, would return $1$ if this constraint is met.}
+
+    Furthermore, the node must not be the occupant of a list.
+
+    \description
+
+    The \verb|lnode_return| function returns a node back to the node pool from
+    which it came. The node must not be subsequently used as an argument to any
+    List functions, until it happens to be allocated again. The pointer to
+    the node object remains valid, and may be returned by a subsequent
+    allocation request from the same node pool.
+
+\subsection{Implementation}
+\index{List!reference implementation}
+
+This section describes the elements of the reference implementation of the
+List component. No requirement is imposed that an implementation should
+follow the reference implementation. The same is true of the
+implementation notes for the other components.
+
+\subsubsection{Types}
+\index{implementation!List types}
+\index{typedefs!implementation of List}
+
+The reference List implementation is a doubly-linked circular list
+\index{sentinel node!of linked list}
+with a {\it sentinel node}. The node structure type is defined like this:
+\begin{verbatim}
+    typedef struct lnode_t {
+        struct lnode_t *list_next;
+        struct lnode_t *list_prev;
+        void *list_data;
+    } lnode_t;
+\end{verbatim}
+and the list structure is defined like this:
+\begin{verbatim}
+    typedef struct list_t {
+        lnode_t list_nilnode;
+        listcount_t list_nodecount;
+        listcount_t list_maxcount;
+    } list_t;
+\end{verbatim}
+The \verb|list_nilnode| member of the list object is the sentinel. It is
+always present in the list, never deleted. When the list is empty, the sentinel
+node's \verb|list_next| and \verb|list_prev| pointers simply point back at the sentinel
+node.  The \verb|list_maxcount| member of the list tells how many nodes may be
+inserted and \verb|list_nodecount| keeps track of the actual count.
+
+The reason the sentinel node is called \verb|list_nilnode| is that it
+acts as the successor of a list's tail node, if there is one,
+and as the predecessor of the first node.  In a linked list implementation
+that does not use a sentinel node, the \verb|list_next| pointer of
+the the tail node and the \verb|list_prev| pointer of the first node would
+be null.
+
+Note that prefixed names are used for all of the structure members.  This is so
+that the header file conforms to the documented namespace. If, for example, the
+\verb|list_nilnode| member were simply called \verb|nilnode|, then
+if the program contained somewhere a macro called \verb|nilnode|, there would
+be a potential clash. If the program defined \verb|nilnode| prior to including
+the \verb|list.h| header, the declaration of \verb|struct list_t| would
+be confounded. If the program defined \verb|nilnode| after 
+including \verb|list.h|, the definition would interfere with \verb|list.h|
+macros whose replacement text refers to the \verb|nilnode| member.
+
+For programming convenience, the list implementation source file defines short
+macro names for the structure members:
+\begin{verbatim}
+    #define next list_next
+    #define prev list_prev
+    #define data list_data
+\end{verbatim}
+... and so forth. These names are private to the translation unit, which
+includes only standard ANSI C headers.  Some of the examples in this section
+make use of the short names; it is assumed that these macros are in effect.
+
+\subsubsection{Selected operations}
+\index{implementation!List operations}
+
+\paragraph{Retrieving the first node}
+\index{List!first node}
+
+Given a pointer \verb|P| to a \verb|list_t| type, the \verb|list_first|
+function examines the value of \verb|P->nilnode.next| which points
+at the head node if the list is not empty. If the list is empty,
+then this expression points back at the sentinel node. In
+other words, the comparison
+\begin{verbatim}
+    P->nilnode.next == &P->nilnode
+\end{verbatim}
+yields true when the list is empty. In this case, the interface requires that
+a null pointer be returned by \verb|list_first|.  The implementation actually
+uses the above test, through a test for \verb|P->nodecount| being equal to
+zero is also possible.
+
+In general, any operation which produces a pointer to the nilnode that must be
+returned back to the calling program  must test for that case and return a null
+pointer instead to satisfy the interface requirements.
+
+\paragraph{Node deletion}
+\index{List!deletion}
+
+Thanks to the use of the sentinel node, the list deletion operation doesn't
+have to test for special cases. A node in the middle of the list is
+deleted in exactly the same way as the first or the last node:
+\begin{verbatim}
+    lnode_t *list_delete(list_t *list, lnode_t *del)
+    {
+        lnode_t *next = del->next;
+        lnode_t *prev = del->prev;
+
+        assert (list_contains(list, del));
+
+        prev->next = next;
+        next->prev = prev;
+        list->nodecount--;
+
+        del->next = del->prev = NULL;
+
+        return del;
+    }
+\end{verbatim}
+Quite simply, the successor and predecessor of the deleted node are connected
+together so that the deleted node is spliced out from the list. If the node is
+the last remaining one, then the sentinel node serves as both the successor and
+the predecessor. The effect of the deletion then is to set the sentinel's next
+and previous links to point to itself, as they did initially when the list was
+previously empty.
+
+The next and prev pointers are set to null not only for enhanced error checking
+in language implementations that trap dereferences of null pointers,
+but also to indicate that the node is not on any list. The interface
+function \verb|lnode_is_in_a_list| makes use of this.
+
+It's worth discussing in some detail why the values of expressions
+\verb|del->next| and \verb|del->prev| are cached in local variables.  The
+actual statements that splice the node out of the list could instead have been
+written:
+\begin{verbatim}
+    del->prev->next = del->next;
+    del->next->prev = del->prev;
+\end{verbatim}
+However, this causes some compilers to generate less than optimal code because
+they fail to apply common subexpression elimination to the double
+occurrence of \verb|del->next|.  Caching this expression in a local variable
+helps to get better code by making the semantics more obvious.  In any case,
+modern compilers tend to do a good job of caching locals in high speed storage,
+particularly on architectures generously endowed with registers, so using a few
+extra locals is unlikely to lead to worse target code. The principle of using
+local variables to perform ``manual CSE'' is applied throughout the Kazlib
+reference implementation.
+
+\paragraph{Node insertion}
+Node insertion is also simple, thanks to the sentinel node which makes
+the doubly linked list circular. All insertions are done using
+the functions \verb|list_ins_before| and \verb|list_ins_after|.
+These are very similar, so it suffices to show \verb|list_ins_before|:
+\begin{verbatim}
+    void list_ins_before(list_t *list, lnode_t *new, lnode_t *this)
+    {
+        lnode_t *that = this->prev;
+
+        assert (new != NULL);
+        assert (!list_contains(list, new));
+        assert (!lnode_is_in_a_list(new));
+        assert (this == list_nil(list) || list_contains(list, this));
+        assert (list->nodecount + 1 > list->nodecount);
+
+        new->next = this;
+        new->prev = that;
+        that->next = new;
+        this->prev = new;
+        list->nodecount++;
+
+        assert (list->nodecount <= list->maxcount);
+    }
+\end{verbatim}
+The node \verb|this| is the one before which the new node is being
+inserted. Internally, the pointer \verb|that| points to the
+node after which the insertion takes place. In other words, the function
+inserts the node \verb|new| in between \verb|this| and \verb|that|.
+
+Note the copious assertions which verify all of the documented constraints:
+that the node is not already on the list, or any other list, that the reference
+node \verb|this| is in the list, and that the list capacity won't be exceeded,
+and that the node count doesn't overflow its type.
+
+\index{List!insertion}
+
+\section{Hash component}
+
+The Hash component provides a means to manage collections of elements, called
+hashes, that are not ordered. Each element in the collection has a unique key,
+which is used for searching and inserting. The intent is that the
+implementation is based on extendible hashing, and the interface allows for
+user-defined hashing functions. The number of elements that can be stored
+in a hash is limited; maximum number of entries in a hash is known as its
+{\it capacity}.
+
+\subsection{Interface}
+
+\subsubsection{The {\tt hash.h} header}
+
+Each C or C++ translation unit that is to use the functionality of the Hash
+component shall include the header \verb|hash.h|. This header shall
+contain declarations of types and external functions, and definitions of
+macros.  The following typedef names shall be
+defined:\index{Hash!typedef names}
+\index{typedefs!defined by Hash}
+\begin{verbatim}
+    hash_t                      hashcount_t
+    hnode_t                     hash_val_t
+    hash_comp_t                 hnode_alloc_t
+    hscan_t                     hnode_free_t
+    hash_fun_t
+\end{verbatim}
+In addition, the following structure tags may be defined:\index{Hash!tag names}
+\index{tags!defined by Hash}
+\begin{verbatim}
+    struct hash_t
+    struct hnode_t
+    struct hscan_t
+\end{verbatim}
+The following external function names shall be declared:
+\index{Hash!function names}\index{functions!defined by Hash}
+\begin{verbatim}
+    hash_create                 hash_count           
+    hash_set_allocator          hash_size            
+    hash_destroy                hash_isfull          
+    hash_free_nodes             hash_isempty         
+    hash_init                   hash_scan_begin      
+    hash_insert                 hash_scan_next       
+    hash_lookup                 hash_scan_delete     
+    hash_delete                 hash_scan_delfree
+    hash_alloc_insert           hash_verify          
+    hash_delete_free            hnode_create         
+    hnode_put                   hnode_init           
+    hnode_get                   hnode_destroy        
+    hnode_getkey                hash_free
+\end{verbatim}
+\index{Hash!external objects}
+In addition, the external object name
+\begin{verbatim}
+    hash_val_t_bit
+\end{verbatim}
+shall be declared.  The following preprocessor symbols (macros) shall be
+defined: \index{Hash!macro names}\index{macros!defined by Hash}
+\indexmacro{HASHCOUNT_T_MAX}
+\indexmacro{HASH_VAL_T_BIT}
+\indexmacro{HASH_VAL_T_MAX}
+\indexmacro{HASH_H}
+\begin{verbatim}
+    HASHCOUNT_T_MAX
+    HASH_VAL_T_BIT
+    HASH_H\end{verbatim}
+\index{symbols!reserved by Hash}\index{Hash!reserved symbols}
+Macro identifiers which begin with the upper-case prefix \verb|HASH| are
+reserved for future extensions to the \verb|hash.h| header, as are
+names in the ordinary and tag namespaces which begin with \verb|hash_|,
+\verb|hnode_| or \verb|hscan_|. External names which begin with \verb|hash_|,
+\verb|hnode_| or \verb|hscan_| are reserved by the Kazlib library regardless of
+what headers are included.
+
+\subsubsection{The {\tt hash_t} type}
+
+\indextype{hash_t}
+The type \verb|hash_t| is an opaque data type which maintains information about
+the current state of a single hash.  From the programmer's viewpoint, a hash
+consists of an instance of the \verb|hash_t| type, plus zero or more instances
+of the type \verb|hnode_t|. An instance of the \verb|hash_t| type can be
+dynamically created using the \verb|hash_create| function, and destroyed by the
+\verb|hash_destroy| function.  Alternately, the program can declare an object
+of type \verb|hash_t| and have it initialized via the \verb|hash_init|
+function.  When initializing a hash this way, the user must also provide
+a fixed-size array of \verb|hnode_t *| objects which serves as the hash table.
+\footnote{A hash initialized this way does not support extendible hashing,
+because there is no mechanism for growing the user-supplied array.}
+
+\subsubsection{The {\tt hnode_t} type}
+
+\indextype{hnode_t}
+The \verb|hnode_t| type is an opaque type that represents a single element
+that can be inserted into a hash. A hash node contains a a reference to
+satellite data provided by the user.  Nodes may be dynamically created by the
+\verb|hnode_create| function.  Alternately, the program may supply an
+\verb|hnode_t| object that can be initialized by the \verb|hnode_init|
+function. 
+
+\subsubsection{The {\tt hash_comp_t} type}
+
+\indextype{hash_comp_t}
+The \verb|hash_comp_t| type is a typedef name for the pointer-to-function type
+\begin{verbatim}
+    int (*)(const void *, const void *);
+\end{verbatim}
+In the context of the Hash component, this type denotes pointers to
+comparison functions.
+
+\subsubsection{The {\tt hscan_t} type}
+
+\indextype{hscan_t}
+The \verb|hscan_t| typedef stands for an opaque type which represents
+context information for traversing a hash. It is initialized by the
+\verb|hash_scan_begin| function, which specifies a hash to be
+traversed. Successive elements are retrieved using the \verb|hash_scan_next|
+function, which eventually indicates that no more elements
+remain. Inserting to, or deleting from a hash other than using
+the function \verb|hash_scan_delete| causes any \verb|hscan_t|
+objects that refer to it to become indeterminate.
+
+\subsubsection{The {\tt hashcount_t} type}
+
+\indextype{hashcount_t}
+\indexmacro{HASHCOUNT_T_MAX}
+This is an unsigned integral type which is capable of representing the number
+of nodes in a hash. 
+The \verb|HASHCOUNT_T_MAX| macro expands to a
+constant expression of type \verb|hashcount_t| which specifies the maximum
+value of that type.
+
+\subsubsection{The {\tt hash_val_t} type}
+
+\indextype{hash_val_t}
+\indexmacro{HASH_VAL_T_MAX}
+The \verb|hash_val_t| type is an unsigned integral type capable of
+holding at least 32 bits. The purpose of this type is to represent the
+output values of hashing functions.
+The \verb|HASH_VAL_T_MAX| macro expands to a
+constant expression of type \verb|hash_val_t| which specifies the maximum
+value of that type.
+
+\subsubsection{The {\tt hnode_alloc_t} type}
+
+\index{Hash!allocator function}
+The \verb|hnode_alloc_t| identifier is a typedef name for the pointer-to-function
+type
+\begin{verbatim}
+    hnode_t *(*)(void *);
+\end{verbatim}
+In other words, a pointer to a function that takes a \verb|void *|
+argument and returns a pointer to \verb|hnode_t|.
+A function of this type which meets certain behavior criteria may be
+registered with a \verb|hash_t| object as node allocator, together
+with a compatible deallocator function. The \verb|void *| argument
+passes user-specified context information through to the
+allocator routines (see section \ref{section:hash_set_allocator}).
+
+\subsubsection{The {\tt hnode_free_t} type}
+
+\index{Hash!deallocator function}
+The \verb|hnode_free_t| identifier is a typedef name for the
+pointer-to-function type
+\begin{verbatim}
+    void (*)(hnode_t *, void *);
+\end{verbatim}
+A function of this type which meets certain behavior criteria may be
+registered with a \verb|hash_t| object as node deallocator
+together with a compatible allocator function.
+
+\subsubsection{The {\tt hash_fun_t} type}
+
+\index{hashing function}
+The \verb|hash_fun_t| identifier is a typedef name for the
+pointer-to-function type
+\begin{verbatim}
+    hash_val_t (*hash_fun_t)(const void *);
+\end{verbatim}
+A function of this type which behaves a certain way is called
+a {\it hashing function}.  To be a viable hashing function, such
+a function must take a pointer to a key object, and produce
+an integer value that depends only on the contents of the key,
+and possibly on information that does not change over the lifetime of any hash
+for which that hashing function is used.  Additional requirements for hashing
+functions are introduced later.
+
+\subsubsection{The {\tt hash_val_t_bit} object}
+
+    \indexobject{hash_val_t_bit}
+    \synopsis
+    \begin{verbatim}
+    extern int hash_val_t_bit;\end{verbatim}
+
+    \description
+
+    The \verb|hash_val_t_bit| object of type int has a fixed value
+    which counts the number of bits in the \verb|hash_val_t| object.
+    The program shall not store a value into this object.
+    
+    The value of \verb|hash_val_t_bit| need not be correct until the
+    first successful call to \verb|hash_create| or to \verb|hash_init|
+    completes.
+
+    The implementation shall provide the macro \verb|HASH_VAL_T_BIT| which
+    expands to a non-lvalue expression that has the same value and type as the
+    object, but which may be a constant expression.\footnote{The intent of
+    providing these values is to ease the implementation of portable hashing
+    functions that take advantage of all of the available bits of a given
+    Kazlib implementation. Alternately, hashing functions may be constructed to
+    only use the lower 32 bits of the type.}
+
+\subsubsection{The {\tt hash_create} function}
+
+    \indexfunc{hash_create}
+    \index{Hash!creation of}
+    \index{create!hash object}
+    \synopsis
+    \begin{verbatim}
+    hash_t *hash_create(hashcount_t, hash_comp_t, hash_fun_t);\end{verbatim}
+
+    \description
+
+    If sufficient resources exist, the \verb|hash_create| function instantiates
+    and initializes an object of type \verb|hash_t| and returns a pointer to
+    it. Otherwise it returns a null pointer.
+
+    The first argument establishes the capacity of the hash, which is
+    initially empty.
+
+    The second argument is a pointer to a comparison function that will be
+    associated with the \verb|hash_t| object for its entire duration.
+
+    \index{hashing function}
+    The third argument is either null or a pointer to a hashing function
+    that is permanently associated with the object. If it is null, a {\it default
+    hashing function\/} is assigned by the implementation.
+
+    The hashing function shall be invoked with an argument that is one
+    of the keys that are being inserted into, or sought after, in the
+    hash. The hashing function must produce the same value each time it
+    is called for a given key. It is up to the hash user to define the
+    representation of keys, to manage their storage, and to provide a matching
+    hashing function.  The hash stores only generic \verb|void *| pointers to
+    keys. 
+
+    The default hashing function assumes that keys are null terminated
+    strings. That is to say, it behaves as though its \verb|void *|
+    argument points to the first elements of an array of \verb|unsigned|
+    \verb|char|, the last of which is a null character. The use of
+    the default hashing function with keys that do not have this representation
+    results in undefined behavior.
+    
+\subsubsection{The {\tt hash_set_allocator} function}
+
+    \indexfunc{hash_set_allocator}
+    \label{section:hash_set_allocator}
+
+    \synopsis
+    \begin{verbatim}
+    void hash_set_allocator(hash_t *, hnode_alloc_t,
+            hnode_free_t, void *);\end{verbatim}
+
+    \constraints
+
+    The second and third arguments---the function pointers---shall either
+    both be null, or both be non-null. The hash pointed at by the first
+    argument shall be empty.
+
+    \description
+
+    When a hash is initialized, it is outfitted with a pair of default
+    node allocation functions. These functions may be replaced with functions
+    supplied by the program by calling the \verb|hash_set_allocator| function
+    and specifying two suitable pointers. If these pointers are null, the
+    default functions are restored.
+
+    These functions are called to allocate and free \verb|hnode_t| 
+    objects by the functions \verb|hash_alloc_insert|
+    and \verb|hash_delete_free| (see sections
+    \ref{section:hash_delete_free} and \ref{section:hash_alloc_insert}).
+
+    If sufficient resources exist, the allocation function shall
+    return a pointer to a unique storage object that is large enough
+    and suitably aligned to represent an object of type \verb|dnode_t|.
+    Otherwise, the function shall return a null pointer.
+
+    The deallocation function shall be capable of disposing of the
+    objects created by the matching allocator function.
+
+
+\subsubsection{The {\tt hash_destroy} function}
+
+    \indexfunc{hash_destroy}
+    \synopsis
+    \begin{verbatim}
+    void hash_destroy(hash_t *);\end{verbatim}
+
+    \constraints
+
+    The hash pointed at by the first argument shall be empty.
+
+    \description
+
+    The \verb|hash_destroy| function deinitializes and deallocates a hash
+    that was created with \verb|hash_create|.
+    All pointers and \verb|hscan_t| objects that referred to the hash become
+    indeterminate.
+
+\subsubsection{The {\tt hash_free_nodes} function}
+
+    \indexfunc{hash_free_nodes}
+    \synopsis
+    \begin{verbatim}
+    void hash_free_nodes(hash_t *);\end{verbatim}
+
+    \description
+
+    The \verb|hash_free_nodes| function removes each node from
+    the hash and destroys it as if by calling \verb|hash_delete_free|
+    (Section \ref{section:hash_delete_free}). The order in which
+    the nodes are destroyed is unspecified.
+
+\subsubsection{The {\tt hash_free} function}
+
+    \indexfunc{hash_free}
+    \synopsis
+    \begin{verbatim}
+    void hash_free(hash_t *);\end{verbatim}
+
+    \description
+
+    Every node in the hash is removed from the hash and is then subject to the
+    deallocation function. The overall effect is as if the function
+    \verb|hash_delete_free| (Section \ref{section:hash_delete_free}) were
+    invoked on each node, and then \verb|hash_destroy| invoked on the
+    hash itself.
+
+    This function is obsolescent, and will be removed from some future revision
+    of this document.
+
+\subsubsection{The {\tt hash_init} function}
+
+    \indexfunc{hash_init}
+    \synopsis
+    \begin{verbatim}
+    hash_t *hash_init(hash_t *, hashcount_t, hash_comp_t,
+        hash_fun_t, hnode_t **, hashcount_t);
+    \end{verbatim}
+
+    \constraints
+
+    The last argument, which specifies the size of the program-supplied table,
+    shall be integral power of two that is greater than one---that is to say, an
+    integer of the form $2^k$ where $k$ is a positive integer.
+ 
+    \description
+ 
+    The \verb|hash_init| function configures the specified \verb|hash_t| object
+    to use a specified array of \verb|hnode_t *| pointer objects as a table.
+    The user is responsible for providing storage for the \verb|hash_t|
+    object and the array. As in the \verb|hash_create| interface,
+    the second parameter specifies the capacity, and the subsequent
+    arguments specify the comparison and hashing function, respectively.
+    The last two arguments specify the table of pointers. The array object
+    shall have at least as many elements as indicated by the last parameter,
+    otherwise the behavior is undefined. The call to \verb|hash_init| is said
+    to register the array with the hash.
+
+    The program shall not register the same array with more than one hash.
+    More specifically, once the program modifies a registered array, or
+    registers it with another hash, it must discontinue use of the first hash.
+    \footnote{Note that no explicit deinitialization function is provided to
+    dissociate the array. A program disposes of a hash created by
+    {\tt hash_init} by discontinuing its use.}
+ 
+\subsubsection{The {\tt hash_insert} function}
+
+    \indexfunc{hash_insert}
+    \label{section:hash_insert}
+    \synopsis
+    \begin{verbatim}
+    void hash_insert(hash_t *, hnode_t *, const void *);\end{verbatim}
+
+    \constraints
+    The hash is not full.  The key specified by the \verb|void *| parameter
+    does not already exist in the specified hash. The node specified
+    by the second parameter is not already inserted into a hash.
+
+    \description
+    The \verb|hash_insert| function adds a new node to a hash. The user
+    must supply a node object that was initialized with \verb|hnode_init|
+    or dynamically created with \verb|hnode_create|. If the node is
+    already inserted into the same hash or any other hash, the behavior
+    is undefined.
+
+    A program may modify a key or node that has been inserted into a hash, or
+    cause the storage of the key or the node to become invalid.  However, any
+    subsequent use of the hash invokes undefined behavior, with the following
+    exception: the data pointer stored within a node may be modified using the
+    \verb|hnode_put| function.
+
+    The \verb|hash_insert| function invokes the hashing function callback with
+    the key pointer as the argument.
+
+    The \verb|hash_insert| function may need to acquire additional storage in
+    order to support hash table growth. If the storage allocation fails, the
+    function shall fully recover, and insert the node without growing the
+    table.
+
+    The Hash implementation shall not modify the storage referenced by a key,
+    and shall not access it other than indirectly through the supplied hashing
+    and comparison functions.
+
+\subsubsection{The {\tt hash_lookup} function}
+
+    \indexfunc{hash_lookup}
+    \synopsis
+    \begin{verbatim}
+    hnode_t *hash_lookup(hash_t *, const void *);\end{verbatim}
+
+    \description
+
+    The \verb|hash_lookup| function searches the given hash for a node
+    matching the given key. Unless the hash is empty, the key shall be
+    compared against one or more keys that are already in the hash,
+    using the comparison function. The key pointer may
+    be identical to one that has already been inserted into the
+    hash.\footnote {In that case, the comparison function must correctly
+    cope with aliased parameters}.
+
+    If the key is found in the hash, a pointer to the corresponding node
+    is returned.\footnote{The corresponding node is the one that was specified
+    in the call to {\tt hash_insert} together with the matching key.}
+
+    If the key is not found, a null pointer is returned.
+
+\subsubsection{The {\tt hash_delete} function}
+
+    \indexfunc{hash_delete}
+    \synopsis
+    \begin{verbatim}
+    hnode_t *hash_delete(hash_t *, hnode_t *);\end{verbatim}
+
+    \constraints
+    The specified node is an occupant of the given hash.
+
+    \description
+    The \verb|hash_delete| function removes from the given hash a
+    node that has previously been inserted into it. The key under
+    which the node was inserted is also removed from the hash.\footnote{Thus
+    the program may arbitrarily manipulate the removed key without destroying
+    the integrity of the hash.}
+
+    Any existing \verb|hscan_t| iterator which is associated with the
+    hash becomes indeterminate.\footnote{To delete the current node during hash
+    table traversal, the {\tt hash_scan_delete} function must be used
+    instead.}
+
+
+\subsubsection{The {\tt hash_alloc_insert} function}
+
+    \label{section:hash_alloc_insert}
+    \indexfunc{hash_alloc_insert}
+
+    \synopsis
+    \begin{verbatim}
+    int hash_alloc_insert(hash_t *, const void *, void *);\end{verbatim}
+
+    \constraints
+
+    The second argument specifies the insertion key. The hash shall not
+    already contain this key.
+
+    \description
+
+    The \verb|hash_alloc_insert| function dynamically allocates and
+    initializes a \verb|hnode_t| object and inserts it into the 
+    given hash. The second argument and third arguments are pointers
+    to user data and key objects, either of which may be null.
+
+    The allocation is performed by a call to the default allocation
+    function, or to the function that was configured using
+    \verb|hash_set_allocator| (Section \ref{section:hash_set_allocator}).
+
+    If the allocation succeeds, the insertion is performed and
+    the value 1 is returned.  If the allocation fails, no insertion is
+    performed and 0 is returned.
+
+\subsubsection{The {\tt hash_delete_free} function}
+
+    \label{section:hash_delete_free}
+    \indexfunc{hash_delete_free}
+
+    \synopsis
+    \begin{verbatim}
+    void hash_delete_free(hash_t *, hnode_t *)
+    \end{verbatim}
+
+    \constraints
+    The given node can be found within the given hash.
+   
+    \description
+    The \verb|hash_delete_free| function is the reverse of
+    \verb|hash_alloc_insert|. It removes the given node form the
+    hash as if by a call to \verb|hash_delete| and then deletes it using the
+    default or user-defined allocator (Section
+    \ref{section:hash_set_allocator}). If the given node had not been created
+    using \verb|hash_alloc_insert|, the behavior is undefined.
+
+\subsubsection{The {\tt hnode_put} function}
+
+    \indexfunc{hnode_put}
+    \synopsis
+    \begin{verbatim}
+    void hnode_put(hnode_t *, void *);\end{verbatim}
+
+    \description
+    The function \verb|hnode_put| replaces the data element
+    associated with the hash node.
+
+\subsubsection{The {\tt hnode_get} function}
+
+    \indexfunc{hnode_get}
+    \synopsis
+    \begin{verbatim}
+    void *hnode_get(hnode_t *);\end{verbatim}
+
+    \description
+    The \verb|hnode_get| function retrieves the \verb|void * | data value
+    associated with the given hash node.
+
+\subsubsection{The {\tt hnode_getkey} function}
+
+    \indexfunc{hnode_getkey}
+    \synopsis
+    \begin{verbatim}
+    const void *hnode_getkey(hnode_t *);\end{verbatim}
+
+    \description
+
+    The \verb|hnode_getkey| function retrieves the \verb|void *| key value 
+    associated with the given node. A node acquires an associated key
+    when it is inserted into a hash (see section \ref{section:hash_insert}).
+    Invoking \verb|hnode_getkey| on a node that has not been inserted
+    into a hash results in undefined behavior.
+
+\subsubsection{The {\tt hash_count} function}
+
+    \indexfunc{hash_count}
+    \synopsis
+    \begin{verbatim}
+    hashcount_t hash_count(hash_t *);\end{verbatim}
+
+    \description
+    The \verb|hash_count| function returns a value which represents the number
+    of nodes currently stored in the hash pointed at by the argument.
+
+\subsubsection{The {\tt hash_size} function}
+
+    \indexfunc{hash_size}
+    \synopsis
+    \begin{verbatim}
+    hashcount_t hash_size(hash_t *hash)\end{verbatim}
+
+    \description
+    The \verb|hash_size| function returns an implementation-defined value that
+    depends on the number of entries in the given hash.  The intent is that the
+    value represent the size of the internal hash table managed by the given
+    hash.
+
+\subsubsection{The {\tt hash_isfull} function}
+
+    \indexfunc{hash_isfull}
+    \synopsis
+    \begin{verbatim}
+    int hash_isfull(hash_t *);\end{verbatim}
+
+    \description
+    The \verb|hash_isfull| function returns 1 if the hash is full,
+    otherwise it returns 0.
+
+    If the argument is an expression with side effects, the behavior is
+    undefined.\index{macros!and side effects}
+
+
+\subsubsection{The {\tt hash_isempty} function}
+
+    \indexfunc{hash_isempty}
+    \synopsis
+    \begin{verbatim}
+    int hash_isempty(hash_t *);\end{verbatim}
+
+    \description
+    The \verb|hash_isempty| function returns 1 if the given hash is empty,
+    otherwise it returns 0.
+
+\subsubsection{The {\tt hash_scan_begin} function}
+
+    \indexfunc{hash_scan_begin}
+    \synopsis
+    \begin{verbatim}
+    void hash_scan_begin(hscan_t *, hash_t *);\end{verbatim}
+
+    \description
+    The \verb|hash_scan_begin| initializes the \verb|hscan_t| iterator object,
+    preparing it for a traversal of the given hash.
+
+    After this initialization, if the hash is modified in any way by
+    the performance of an insertion or deletion operation, the
+    value of the \verb|hscan_t| object becomes indeterminate,
+    with one exception:  the \verb|hash_scan_delete| function or the
+    \verb|hash_scan_delfree| function may be used to delete the current
+    node.
+
+\subsubsection{The {\tt hash_scan_next} function}
+
+    \indexfunc{hash_scan_next}
+    \synopsis
+    \begin{verbatim}
+    hnode_t *hash_scan_next(hscan_t *);\end{verbatim}
+
+    \description
+    If any unvisited nodes remain, the \verb|hash_scan_next| function advances
+    to the next one and returns a pointer to it. Otherwise, it returns a null
+    pointer. Repeated invocations of \verb|hash_scan_next| return a pointer to
+    every node that has been inserted into the table, in no particular order,
+    such that no node is reported twice.
+
+\subsubsection{The {\tt hash_scan_delete} function}
+
+    \indexfunc{hash_scan_delete}
+    \synopsis
+    \begin{verbatim}
+    hnode_t *hash_scan_delete(hash_t *, hnode_t *);
+    \end{verbatim}
+
+    \constraints
+    The specified node is an occupant of the given hash.
+
+    \description
+    This function is almost exactly like \verb|hash_delete| except that it may
+    be used to delete a node that has been most recently obtained from
+    \verb|hash_scan_next| without destroying the validity of the \verb|hscan_t|
+    iterator from which the node was obtained.
+
+\subsubsection{The {\tt hash_scan_delfree} function}
+
+    \label{section:hash_scan_delfree}
+    \indexfunc{hash_scan_delfree}
+
+    \synopsis
+    \begin{verbatim}
+    void hash_scan_delfree(hash_t *, hnode_t *)
+    \end{verbatim}
+
+    \constraints
+    The given node can be found within the given hash.
+   
+    \description
+    The \verb|hash_scan_delfree| function is similar to
+    \verb|hash_delete_free|. It removes the given node form the
+    hash and then deletes it using the default or user-defined allocator
+    (Section \ref{section:hash_set_allocator}). If the given node
+    had not been created using \verb|hash_alloc_insert|, the behavior
+    is undefined.
+
+    The deletion from the hash is performed as if by a call to
+    \verb|hash_scan_delete|, thus it is safe to delete a node that
+    was most recently obtained from a \verb|hash_scan_next| without
+    destroying the validity of the \verb|hscan_t| iterator.
+
+\subsubsection{The {\tt hash_verify} function}
+
+    \indexfunc{hash_verify}
+    \synopsis
+    \begin{verbatim}
+    int hash_verify(hash_t *hash);\end{verbatim}
+
+    \description
+    The intent of the \verb|hash_verify| function is to perform a verification
+    on the hash object, regardless of whether the Kazlib implementation is
+    operated in verification or production mode. If the hash object
+    and its constituent nodes have been correctly manipulated, and the
+    program has not caused any undefined behaviors, the value $1$ is returned.
+    Otherwise, the function may be able to, but is not guaranteed to, detect
+    corruption, and return the value zero.
+
+\subsubsection{The {\tt hnode_create} function}
+
+    \indexfunc{hnode_create}
+    \synopsis
+    \begin{verbatim}
+    hnode_t *hnode_create(void *);\end{verbatim}
+
+    \description
+    The \verb|hnode_create| function dynamically allocates a hash node,
+    stores in it the data value specified in the argument and
+    returns a pointer to it. The allocation is performed by a call to the
+    standard \verb|malloc| function. If the allocation fails, a null
+    pointer is returned.
+
+    The node's key pointer remains indeterminate until it is the subject of a
+    \verb|hash_insert| operation.
+
+\subsubsection{The {\tt hnode_init} function}
+
+    \indexfunc{hnode_init}
+    \synopsis
+    \begin{verbatim}
+    hnode_t *hnode_init(hnode_t *, void *);\end{verbatim}
+
+    \description
+    The \verb|hnode_init| function initializes the contents
+    of the specified hash node object, assigning it the
+    data value specified as the second argument. 
+    The first argument is a pointer which refers to 
+    a data object that has a suitable size and alignment
+    for the representation of an \verb|hnode_t| type.
+    After initialization with \verb|hnode_init|, the object is subsequently
+    eligible as an operand to the functions of the hash component,
+    other than \verb|hnode_getkey|.
+
+    The node's key pointer remains indeterminate until it is the subject of a
+    \verb|hash_insert| operation.
+
+\subsubsection{The {\tt hnode_destroy} function}
+
+    \indexfunc{hnode_destroy}
+    \synopsis
+    \begin{verbatim}
+    void hnode_destroy(hnode_t *);\end{verbatim}
+
+    \description
+    The \verb|hnode_destroy| function destroys a hash node that has been
+    allocated with the \verb|hnode_create| function.  The value of any pointer
+    that referred to the node that was thus freed is indeterminate.
+
+    If the node is currently the occupant of a hash, the behavior is undefined
+    if the hash is subsequently used.
+
+\subsection{Implementation}
+
+TODO
+
+\section{Dictionary component}
+
+\index{Dictionary}
+The Dictionary component provides a means to manage ordered sequences of
+elements, having the following properties:
+\begin{enumerate}
+\item If the dictionary is not empty, a first and last element can be identified.
+    In a dictionary having only one element, that one element is both the first and
+    last element.
+\item Each element that is not the last element has another element as its
+    {\it successor}.
+    \index{successor!of a dictionary element}
+    \index{Dictionary!successor of an element}
+\item Each element that is not the first element has a {\it predecessor}.
+    \index{predecessor!of a dictionary element}
+    \index{Dictionary!predecessor of an element}
+\item No element is the predecessor or successor of more than one element.
+\item If one element is the successor of another, the other is necessarily the
+    predecessor of the first.
+\item Each element is associated with a piece of information known as 
+    the key. The sequence is ordered according to the relation imposed
+    by the comparison function: the key of an element compares
+    greater than or equal to the key of its predecessor.
+\item If duplicate keys are present, then elements
+    having the same key form a subsequence with no other keys in it, which
+    follows from the previous property. No additional ordering is imposed
+    within such subsequences.
+\item Each element is associated with arbitrary satellite data.
+\end{enumerate}
+
+The Dictionary component supports efficient operations over such ordered
+sequences: such as insertion, deletion, ordered traversal, as well as exact and
+range searches.\footnote{The implicit association of keys and satellite data,
+together with the ability of efficiently search by key to retrieve data, gives
+rise to the term {\it dictionary}. A dictionary need not be ordered; a hash can
+therefore also be considered to be a kind of dictionary; the Kazlib
+nomenclature is somewhat unfortunate in that regard.}
+
+The number of elements that can be stored in a dictionary is limited; maximum
+number of entries in a dictionary is known as its {\it capacity}.
+
+\subsection{Interface}
+
+\subsubsection{The {\tt dict.h} header}
+
+Each C or C++ translation unit that is to use the functionality of the Dict
+component shall include the header \verb|dict.h|. This header shall
+contain declarations of types and external functions, and definitions of
+macros.  The following typedef names shall be
+defined:\index{Dict!typedef names}
+\index{typedefs!defined by Dict}
+\begin{verbatim}
+    dict_t                      dnode_process_t
+    dnode_t                     dnode_alloc_t
+    dictcount_t                 dnode_free_t
+    dict_comp_t                 dict_load_t
+\end{verbatim}
+In addition, the following structure tags may be defined:\index{Dict!tag names}
+\index{tags!defined by Dict}
+\begin{verbatim}
+    struct dict_t
+    struct dnode_t
+\end{verbatim}
+The following external function names shall be declared:
+\index{Dict!function names}\index{functions!defined by Dict}
+\begin{verbatim}
+    dict_create                 dict_count                     
+    dict_set_allocator          dict_isempty                   
+    dict_destroy                dict_isfull                    
+    dict_free_nodes             dict_contains                  
+    dict_init                   dict_allow_dupes               
+    dict_verify                 dnode_is_in_a_dict             
+    dict_lookup                 dnode_create                   
+    dict_lower_bound            dnode_init                     
+    dict_upper_bound            dnode_destroy                  
+    dict_insert                 dnode_get                      
+    dict_delete                 dnode_getkey                   
+    dict_alloc_insert           dnode_put                      
+    dict_delete_free            dict_process                   
+    dict_first                  dict_load_begin
+    dict_last                   dict_load_next
+    dict_next                   dict_load_end 
+    dict_prev                   dict_free
+\end{verbatim}
+The following preprocessor symbols shall be
+defined: \index{Dict!macro names}\index{macros!defined by Dict}
+\indexmacro{DICTCOUNT_T_MAX}
+\indexmacro{DICT_H}
+\begin{verbatim}
+    DICTCOUNT_T_MAX
+    DICT_H\end{verbatim}
+\index{symbols!reserved by Dict}\index{Dict!reserved symbols}
+Macro identifiers which begin with the upper-case prefix \verb|DICT| are
+reserved for future extensions to the \verb|dict.h| header, as are
+names in the ordinary and tag namespaces which begin with \verb|dict_|
+or \verb|dnode_|. External names which begin with \verb|dict_|
+or \verb|dnode_| are reserved by the Kazlib library regardless of
+what headers are included.
+
+\subsubsection{The {\tt dict_t} type}
+
+\indextype{dict_t}
+The type \verb|dict_t| is an opaque data type which represents a single
+dictionary. A dictionary consists of an instance of the \verb|dict_t| type,
+plus zero or more instances of the type \verb|dnode_t|. An object of type
+\verb|dict_t| can be initialized by the \verb|dict_init| function. Alternately,
+the \verb|dict_create| function will dynamically allocate and initialize a
+dictionary.  An empty dictionary created by \verb|dict_create| may be disposed
+of using \verb|dict_destroy|.
+
+\subsubsection{The {\tt dnode_t} type}
+
+\indextype{dnode_t}
+The \verb|dnode_t| type represents a single entry in a dictionary called a
+dictionary node.  The object stores a pointer to user data, and a key pointer
+that is assigned to the dictionary node at the time when it is inserted into
+the dictionary. A \verb|dnode_t| may be dynamically created using
+\verb|dnode_create| and destroyed using \verb|dnode_destroy|. Alternately,
+the program may supply storage for a \verb|dnode_t| object and initialize
+it using the \verb|dnode_init| function.
+
+\subsubsection{The {\tt dictcount_t} type}
+
+\indextype{dictcount_t}
+\indexmacro{DICTCOUNT_T_MAX}
+This is an unsigned integral type which is capable of representing the number
+of nodes in a dictionary.  The \verb|DICTCOUNT_T_MAX| macro expands to a
+constant expression of type \verb|dictcount_t| which specifies the maximum
+value of that type.
+
+\subsubsection{The {\tt dict_comp_t} type}
+
+\indextype{dict_comp_t}
+The \verb|dict_comp_t| type is a typedef name for the pointer-to-function type
+\begin{verbatim}
+    int (*)(const void *, const void *);
+\end{verbatim}
+In the context of the Dictionary component, this type denotes pointers to
+comparison functions. 
+
+\subsubsection{The {\tt dnode_process_t} type}
+
+\indextype{dnode_process_t}
+The type \verb|dnode_process_t| is a typedef name for the pointer-to-function type
+\begin{verbatim}
+    void (*)(dict_t *, dnode_t *, void *);
+\end{verbatim}
+In the context of the Dictionary component, this is the type of a
+dictionary node processing function (See section \ref{section:dict_process}).
+The first two parameters identify a dictionary and the node within that
+dictionary that is being processed. The third argument is a context pointer.
+
+\subsubsection{The {\tt dnode_alloc_t} type}
+
+\indextype{dnode_alloc_t}
+The type \verb|dnode_alloc_t| is a typedef name for the pointer-to-function type
+\begin{verbatim}
+    dnode_t *(*)(void *);
+\end{verbatim}
+A function compatible with this type which meets certain other criteria may be
+registered with a \verb|dict_t| object as a node allocator function
+(See section \ref{section:dict_set_allocator}).
+
+\subsubsection{The {\tt dnode_free_t} type}
+
+\indextype{dnode_free_t}
+The type \verb|dnode_free_t| is a typedef name for the pointer-to-function type
+\begin{verbatim}
+    void (*)(dnode_t *, void *);
+\end{verbatim}
+A function compatible with this type which meets certain other criteria may be
+registered with a \verb|dict_t| object as a node deallocator function.
+(See section \ref{section:dict_set_allocator}).
+
+\subsubsection{The {\tt dict_load_t} type}
+
+\indextype{dict_load_t}
+
+The \verb|dict_load_t| type is opaque, and represents a context structure
+used during the process of constructing a dictionary from an ordered list
+of nodes.  (See sections \ref{section:dict_load_begin} to
+\ref{section:dict_load_end}).
+
+\subsubsection{The {\tt dict_create} function}
+
+    \indexfunc{dict_create}
+    \index{Dictionary!creation of}
+    \index{create!dictionary object}
+
+    \synopsis
+    \begin{verbatim}
+    dict_t *dict_create(dictcount_t, dict_comp_t);\end{verbatim}
+
+    \description
+    The \verb|dict_create| function allocates a new
+    object of type \verb|dict_t| and initializes it to act as
+    a dictionary.
+    
+    If insufficient resources exist for the allocation,
+    a null pointer is returned, otherwise a pointer to the dictionary
+    is returned.
+    
+    The first argument specifies the capacity of the dictionary,
+    which is initially empty.
+
+    The second argument is a comparison function that is used for comparing
+    keys during insertion and searching operations, and is associated
+    with the dictionary for its entire duration.
+
+\subsubsection{The {\tt dict_set_allocator} function}
+
+    \label{section:dict_set_allocator}
+    \indexfunc{dict_set_allocator}
+
+    \synopsis
+    \begin{verbatim}
+    void dict_set_allocator(dict_t *, dnode_alloc_t,
+            dnode_free_t, void *);\end{verbatim}
+
+    \constraints
+
+    The second and third arguments---the function pointers---shall either
+    both be null, or both be non-null. The dictionary pointed at by the first
+    argument shall be empty.
+
+    \description
+
+    When a dictionary is initialized, it is outfitted with a pair of default
+    node allocation functions. These functions may be replaced with functions
+    supplied by the program by calling the \verb|dict_set_allocator| function
+    and specifying two suitable pointers. If these pointers are null, the
+    default functions are restored.
+
+    These functions are called to allocate and free \verb|dnode_t|
+    objects by the functions \verb|dict_alloc_insert|
+    and \verb|dict_delete_free| (see sections
+    \ref{section:dict_delete_free} and \ref{section:dict_alloc_insert}).
+
+    If sufficient resources exist, the allocation function shall
+    return a pointer to a unique storage object that is large enough
+    and suitably aligned to represent an object of type \verb|dnode_t|.
+    Otherwise, the function shall return a null pointer.
+
+    The deallocation function shall be capable of disposing of the
+    objects created by the matching allocator function.
+
+\subsubsection{The {\tt dict_destroy} function}
+
+    \indexfunc{dict_destroy}
+
+    \synopsis
+    \begin{verbatim}
+    void dict_destroy(dict_t *);\end{verbatim}
+
+    \constraints
+
+    The dictionary pointed at by the first argument shall be empty.
+
+    \description
+
+    The \verb|dict_destroy| function deinitializes and deallocates a dictionary
+    object that was created by \verb|dict_create|.  All pointers that
+    referred to the dictionary become indeterminate.
+
+\subsubsection{The {\tt dict_free_nodes} function}
+
+    \indexfunc{dict_free_nodes}
+
+    \synopsis
+    \begin{verbatim}
+    void dict_free_nodes(dict_t *);\end{verbatim}
+
+    \description
+
+    Every node in the dictionary is removed from the dictionary and is then
+    subject to the deallocation function, as if the function
+    \verb|dict_delete_free| (Section \ref{section:dict_delete_free}) were
+    invoked on each node, in some unspecified order.
+
+\subsubsection{The {\tt dict_free} function}
+
+    \indexfunc{dict_free}
+
+    \synopsis
+    \begin{verbatim}
+    void dict_free(dict_t *);\end{verbatim}
+
+    \description
+
+    This function is obsolescent, and will be removed from some future revision
+    of this document. It is equivalent to \verb|dict_free_nodes|.
+
+\subsubsection{The {\tt dict_init} function}
+
+    \indexfunc{dict_init}
+
+    \synopsis
+    \begin{verbatim}
+    dict_t *dict_init(dict_t *, dictcount_t, dict_comp_t);\end{verbatim}
+
+    \description
+ 
+    The \verb|dict_init| function prepares specified \verb|dict_t| object
+    to behave as a dictionary that may subsequently be used with the other
+    dictionary functions.
+    
+    The first argument points to the \verb|dict_t| object to be initialized.
+    The second argument specifies the capacity of the dictionary. The third
+    argument is a pointer to the comparison function which shall be associated
+    with the dictionary for its entire duration.
+
+\subsubsection{The {\tt dict_verify} function}
+
+    \indexfunc{dict_verify}
+
+    \synopsis
+    \begin{verbatim}
+    int dict_verify(dict_t *);\end{verbatim}
+
+    \description
+
+    The intent of the \verb|dict_verify| function is to perform a verification
+    on the dictionary object, regardless of whether the Kazlib implementation
+    is operated in verification or production mode. If the dictionary object
+    and its constituent nodes have been correctly manipulated, and the program
+    has not caused any undefined behaviors, the value $1$ is returned.
+    Otherwise, the function may be able to, but is not guaranteed to, detect
+    corruption, and return the value zero.
+
+\subsubsection{The {\tt dict_lookup} function}
+
+    \indexfunc{dict_lookup}
+
+    \synopsis
+    \begin{verbatim}
+    dnode_t *dict_lookup(dict_t *, const void *);\end{verbatim}
+
+    \description
+    The \verb|dict_lookup| function searches the given dictionary for a node
+    matching the given key. Unless the dictionary is empty, the key shall be
+    compared against one or more keys that are already in the dictionary, using
+    the comparison function. The key pointer may be identical to one that has
+    already been inserted into the dictionary.
+
+    If the key is found in the dictionary, a pointer to the corresponding node
+    is returned.
+
+    If the key is not found, a null pointer is returned.
+
+    If the dictionary contains more than one key which matches the search
+    key, then the first key in the subsequence of duplicate keys is returned.
+
+\subsubsection{The {\tt dict_lower_bound} function}
+
+    \indexfunc{dict_lower_bound}
+
+    \synopsis
+    \begin{verbatim}
+    dnode_t *dict_lower_bound(dict_t *, const void *);\end{verbatim}
+
+    \description
+
+    The \verb|dict_lower_bound| function searches the dictionary in a manner
+    similar to \verb|dict_lookup|.
+    
+    If the given key exists in the dictionary, the behavior is exactly the same
+    as \verb|dict_lookup|.
+    
+    However, if the key is not found, then the node which has the smallest key
+    that is greater than the search key is returned. If no such key exists
+    (because the search key is higher than any other key in the dictionary
+    or the dictionary is empty) then a null pointer is returned.
+
+    \example
+    Suppose that pointer \verb|d| refers to a dictionary whose registered
+    comparison function performs lexicographic comparisons on ordinary
+    C strings, similar to \verb|strcmp|.  To iterate over all keys that
+    begin with the letter \verb|d|, the following idiom can be used:
+    \begin{verbatim}
+    dict_t *d;
+    dnode_t *n, *start, *end;
+    /*...*/
+    start = dict_lower_bound(d, "d");
+    end = dict_lower_bound(d, "e");
+    for (n = start; n != end; n = dict_next(d, n)) {
+        /* n points to each node in turn whose
+           key starts with 'd' */
+    }
+    \end{verbatim}
+    Note that if the dictionary is empty, or has keys which are all lower
+    than \verb|"d"|, then both \verb|start| and \verb|end| shall be null
+    pointers, and the loop body will never execute since the two are equal.
+    Also note that if there are keys that begin with \verb|d| and the
+    dictionary's last node has a key that starts with \verb|d|, then \verb|end|
+    is null, otherwise \verb|end| points to the first key that doesn't begin
+    with \verb|d|. In both cases, the loop will terminate after processing the
+    last \verb|d| key, because \verb|dict_next| shall produce a pointer that is
+    equal to \verb|end|.
+
+\subsubsection{The {\tt dict_upper_bound} function}
+
+    \indexfunc{dict_upper_bound}
+
+    \synopsis
+    \begin{verbatim}
+    dnode_t *dict_upper_bound(dict_t *, const void *);\end{verbatim}
+
+    \description
+
+    The \verb|dict_upper_bound| function searches the dictionary in a manner
+    similar to \verb|dict_lookup|.
+    
+    If the given key exists in the dictionary, the behavior is exactly the same
+    as \verb|dict_lookup| with one difference: 
+    If the dictionary contains more than one key which matches the search
+    key, then the last key in the sequence of duplicates is returned,
+    rather than the first.
+    
+    However, if the key is not found, then the node which has the greatest key
+    that is lower than the search key is returned. If no such key exists
+    (because the search key is lower than any other key in the dictionary
+    or the dictionary is empty) then a null pointer is returned.
+  
+    \example
+    The following idiom can be used to iterate over a sequence of duplicate
+    keys without the overhead of performing a full comparison before each
+    iteration to detect the first non-matching key. 
+    \begin{verbatim}
+    dict_t *d;
+    void *key;
+    dnode_t *n, *start, *end;
+
+    /* ... Initialize d, and key. ...*/
+    start = dict_lower_bound(d, key);
+    end = dict_upper_bound(d, key);
+
+    /* advance end to first non-matching key */
+    if (end != 0)
+        end = dict_next(d, end);
+    else
+        end = start;	/* start == dict_first(d) in this case */
+
+    for (n = start; n != end; n = dict_next(d, n)) {
+        /* n points to duplicate keys in turn */
+    }
+    \end{verbatim}
+    Immediately prior to the execution of the if statement, exactly one of the
+    following conditions is true:
+    \begin{itemize}
+    \item The key was found in the dictionary; \verb|start| points to the
+	first duplicate node and \verb|end| points to the last.
+    \item The dictionary has only higher keys than the search key; \verb|start|
+	points to the first node in the dictionary and \verb|end| is null.
+    \item The dictionary has only lower keys than the search key; \verb|end|
+	points to the last node in the dictionary, and \verb|start| is null.
+    \item The dictionary has both lower and higher keys; \verb|end| and \verb|start|
+	point to two consecutive nodes, respectively, such that the node
+	pointed at by \verb|end| has a lower key than the search key and
+	the node pointed at by \verb|start| has a higher key.
+    \item The dictionary is empty; \verb|start| and \verb|end| are null.
+    \end{itemize}
+    The if statement ensures that if the dictionary contains no matching
+    keys, than \verb|start| and \verb|end| are equal, and if the dictionary
+    contains one or more matching keys, than \verb|end| points to the first
+    non-matching node, or is null if there is no such node.  Thus the loop
+    performs correctly in all circumstances.
+
+\subsubsection{The {\tt dict_insert} function}
+
+    \label{section:dict_insert}
+    \indexfunc{dict_insert}
+
+    \synopsis
+    \begin{verbatim}
+    void dict_insert(dict_t *, dnode_t *, const void *);\end{verbatim}
+
+    \constraints
+    The dictionary is not full.  If the dictionary has not been configured
+    to allow duplicate keys, the key specified by the \verb|void *| parameter
+    does not already exist in the dictionary.
+
+    \description
+    The \verb|dict_insert| function adds a new node to a dictionary. The user
+    must supply a node object that was initialized with \verb|dnode_init| or
+    dynamically created with \verb|dnode_create|. If the node is already
+    inserted into the same dictionary or any other dictionary, the behavior is
+    undefined.
+
+    Duplicate keys may be inserted into a dictionary only if the dictionary
+    has been configured to permit duplicate keys (see section
+    \ref{section:dict_allow_dupes}). If this is the case, it is also
+    permissible to insert the same key more than once: the implementation shall
+    not distinguish between distinct keys that are declared equal by a
+    correctly designed comparison function, and two key pointers that refer to
+    the same key.
+
+    A program may modify a key or node that has been inserted into a
+    dictionary, or cause the storage of the key or the node to become invalid.
+    However, any subsequent use of the dictionary invokes undefined behavior, with
+    the following exception: the data pointer stored within a node may be
+    modified using the \verb|dnode_put| function.
+
+    The Dictionary implementation shall not modify the storage referenced by a
+    key, and shall not access it other than indirectly through the supplied
+    comparison function.
+
+\subsubsection{The {\tt dict_delete} function}
+
+    \indexfunc{dict_delete}
+
+    \synopsis
+    \begin{verbatim}
+    dnode_t *dict_delete(dict_t *, dnode_t *);\end{verbatim}
+
+    \constraints
+    The specified node is an occupant of the given dictionary.
+
+    \description
+    The \verb|dict_delete| function removes from the given dictionary a
+    node that has previously been inserted into it. The key under
+    which the node was inserted is also removed from the dictionary.
+
+\subsubsection{The {\tt dict_alloc_insert} function}
+
+    \label{section:dict_alloc_insert}
+    \indexfunc{dict_alloc_insert}
+
+    \synopsis
+    \begin{verbatim}
+    int dict_alloc_insert(dict_t *, const void *, void *);\end{verbatim}
+
+    \constraints
+
+    The second argument specifies the insertion key. The dictionary shall not
+    already contain this key unless it has been configured as allowing
+    duplicates.
+
+    \description
+
+    The \verb|dict_alloc_insert| function dynamically allocates and
+    initializes a \verb|dnode_t| object and inserts it into the 
+    given dictionary. The second argument and third arguments are pointers
+    to user data and key objects, either of which may be null.
+
+    The allocation is performed by a call to the default allocation
+    function, or to the function that was configured using
+    \verb|dict_set_allocator| (Section \ref{section:dict_set_allocator}).
+
+    If the allocation succeeds, the insertion is performed and
+    the value 1 is returned.  If the allocation fails, no insertion is
+    performed and 0 is returned.
+
+\subsubsection{The {\tt dict_delete_free} function}
+
+    \label{section:dict_delete_free}
+    \indexfunc{dict_delete_free}
+
+    \synopsis
+    \begin{verbatim}
+    void dict_delete_free(dict_t *, dnode_t *);\end{verbatim}
+
+    \constraints
+    The given node can be found within the given dictionary.
+   
+    \description
+    The \verb|dict_delete_free| function is the reverse of
+    \verb|dict_alloc_insert|. It removes the given node form the
+    dictionary and then deletes it using the default or user-defined allocator
+    (Section \ref{section:dict_set_allocator}). If the given node
+    had not been created using \verb|dict_alloc_insert|, the behavior
+    is undefined.
+
+\subsubsection{The {\tt dict_first} function}
+
+    \indexfunc{dict_first}
+
+    \synopsis
+    \begin{verbatim}
+    dnode_t *dict_first(dict_t *);\end{verbatim}
+
+    \description
+    If the dictionary pointed at by the argument is empty, a null pointer
+    is returned. Otherwise, a pointer to the first node in that dictionary is
+    returned. 
+
+\subsubsection{The {\tt dict_last} function}
+
+    \indexfunc{dict_last}
+
+    \synopsis
+    \begin{verbatim}
+    dnode_t *dict_last(dict_t *);\end{verbatim}
+
+    \description
+    If the dictionary pointed at by the argument is empty, a null pointer
+    is returned. Otherwise, a pointer to the last node in that dictionary is
+    returned. 
+
+
+\subsubsection{The {\tt dict_next} function}
+
+    \indexfunc{dict_next}
+
+    \synopsis
+    \begin{verbatim}
+    dnode_t *dict_next(dict_t *, dnode_t *);\end{verbatim}
+
+    \constraints
+    The node pointed at by the second argument is an occupant of the dictionary
+    pointed at by the first argument.
+
+    \description
+    If the node pointed at by the second argument has a successor, a pointer to
+    that successor is returned. Otherwise, a null pointer is returned.
+
+    \example
+    The \verb|dict_first| and \verb|dict_next| functions can be used together
+    to iterate over all of the elements of the dictionary, as in the following
+    idiom:
+    \begin{verbatim}
+    dict_t *d;
+    dnode_t *n;
+    /*...*/
+    for (n = dict_first(d); n != 0; n = dict_next(d, n)) {
+        /* n points to each node in turn */
+    }
+    \end{verbatim}
+
+\subsubsection{The {\tt dict_prev} function}
+
+    \indexfunc{dict_prev}
+
+    \synopsis
+    \begin{verbatim}
+    dnode_t *dict_prev(dict_t *, dnode_t *);\end{verbatim}
+
+    \constraints
+    The node pointed at by the second argument is an occupant of the dictionary
+    pointed at by the first argument.
+
+    \description
+    If the node pointed at by the second argument has a predecessor, a pointer
+    to that predecessor is returned. Otherwise, a null pointer is returned.
+
+\subsubsection{The {\tt dict_count} function}
+
+    \indexfunc{dict_count}
+
+    \synopsis
+    \begin{verbatim}
+    dictcount_t dict_count(dict_t *);\end{verbatim}
+
+    \description
+    The \verb|dict_count| function returns a value which represents the number
+    of nodes currently stored in the dictionary pointed at by the argument.
+
+\subsubsection{The {\tt dict_isempty} function}
+
+    \indexfunc{dict_isempty}
+
+    \synopsis
+    \begin{verbatim}
+    int dict_isempty(dict_t *);\end{verbatim}
+
+    \description
+    The \verb|dict_isempty| function returns 1 if the given dictionary is
+    empty, otherwise it returns 0.
+
+\subsubsection{The {\tt dict_isfull} function}
+
+    \indexfunc{dict_isfull}
+
+    \synopsis
+    \begin{verbatim}
+    int dict_isfull(dict_t *);\end{verbatim}
+
+    \description
+    The \verb|dict_isfull| function returns 1 if the dictionary is full,
+    otherwise it returns 0.
+
+    If the argument is an expression with side effects, the behavior is
+    undefined.\index{macros!and side effects}
+
+\subsubsection{The {\tt dict_contains} function}
+
+    \indexfunc{dict_contains}
+
+    \synopsis
+    \begin{verbatim}
+    int dict_contains(dict_t *, dnode_t *);\end{verbatim}
+
+    \description
+    The \verb|dict_contains| function searches the given dictionary to
+    determine whether the given node is an occupant. If the node is found, 1 is
+    returned, otherwise 0 is returned.\footnote{The intent is to support
+    verification.  The search may be inefficient compared to {\tt
+    dict_lookup}.}
+
+\subsubsection{The {\tt dict_allow_dupes} function}
+
+    \label{section:dict_allow_dupes}
+    \indexfunc{dict_allow_dupes}
+
+    \synopsis
+    \begin{verbatim}
+    void dict_allow_dupes(dict_t *);\end{verbatim}
+
+    \constraints
+    The dictionary specified by the first argument shall be empty.
+
+    \description
+    The \verb|dict_allow_dupes| function configures the given dictionary to
+    support duplicate keys. This can only be done when the dictionary is empty,
+    and the change cannot be reverted.
+
+\subsubsection{The {\tt dnode_is_in_a_dict} function}
+
+    \indexfunc{dnode_is_in_a_dict}
+
+    \synopsis
+    \begin{verbatim}
+    int dnode_is_in_a_dict(dnode_t *);\end{verbatim}
+
+    \description
+    The \verb|dnode_is_in_a_dict| function reports whether the given node
+    is currently the occupant of some dictionary. If so, 1 is returned.
+    Otherwise 0 is returned.
+
+\subsubsection{The {\tt dnode_create} function}
+
+    \indexfunc{dnode_create}
+
+    \synopsis
+    \begin{verbatim}
+    dnode_t *dnode_create(void *);\end{verbatim}
+
+    \description
+    The \verb|dnode_create| function dynamically allocates a dictionary node,
+    stores in it the data value specified in the argument and
+    returns a pointer to it. The allocation is performed by a call to the
+    standard \verb|malloc| function. If the allocation fails, a null
+    pointer is returned.
+
+    The node's key pointer remains indeterminate until it is the subject of a
+    \verb|dict_insert| operation.
+
+\subsubsection{The {\tt dnode_init} function}
+
+    \indexfunc{dnode_init}
+
+    \synopsis
+    \begin{verbatim}
+    dnode_t *dnode_init(dnode_t *, void *);\end{verbatim}
+
+    \description
+    The \verb|dnode_init| function initializes the contents
+    of the specified dictionary node object, assigning it the
+    data value specified as the second argument. 
+    The first argument is a pointer which refers to 
+    a data object that has a suitable size and alignment
+    for the representation of an \verb|dnode_t| type.
+    After initialization with \verb|dnode_init|, the object is subsequently
+    eligible as an operand to the functions of the dictionary component,
+    other than \verb|dnode_getkey|.
+
+    The node's key pointer remains indeterminate until it is the subject of a
+    \verb|dict_insert| operation.
+
+\subsubsection{The {\tt dnode_destroy} function}
+
+    \indexfunc{dnode_destroy}
+
+    \synopsis
+    \begin{verbatim}
+    void dnode_destroy(dnode_t *);\end{verbatim}
+
+    \description
+    The \verb|dnode_destroy| function destroys a dictionary node that has been
+    allocated with \verb|dnode_create|.  The value of any pointer
+    that referred to the node that was thus freed is indeterminate.
+
+    If the node is currently the occupant of a dictionary, the behavior is
+    undefined if the hash is subsequently used.
+
+\subsubsection{The {\tt dnode_get} function}
+
+    \indexfunc{dnode_get}
+
+    \synopsis
+    \begin{verbatim}
+    void *dnode_get(dnode_t *);\end{verbatim}
+
+    \description
+    The \verb|dnode_get| function retrieves the \verb|void * | data value
+    associated with the given dictionary node.
+
+\subsubsection{The {\tt dnode_getkey} function}
+
+    \indexfunc{dnode_getkey}
+
+    \synopsis
+    \begin{verbatim}
+    const void *dnode_getkey(dnode_t *);\end{verbatim}
+
+    \description
+
+    The \verb|dnode_getkey| function retrieves the \verb|void *| key value 
+    associated with the given node. A node acquires an associated key
+    when it is inserted into a dictionary (see section \ref{section:dict_insert}).
+    Invoking \verb|dnode_getkey| on a node that has not been inserted
+    into a dictionary results in undefined behavior.
+
+\subsubsection{The {\tt dnode_put} function}
+
+    \indexfunc{dnode_put}
+
+    \synopsis
+    \begin{verbatim}
+    void dnode_put(dnode_t *, void *);\end{verbatim}
+
+    \description
+    The function \verb|dnode_put| replaces the data element
+    associated with the dictionary node.
+
+\subsubsection{The {\tt dict_process} function}
+
+    \label{section:dict_process}
+    \indexfunc{dict_process}
+
+    \synopsis
+    \begin{verbatim}
+    void dict_process(dict_t *, void *, dnode_process_t);\end{verbatim}
+
+    \description
+    The \verb|dict_process| function iterates over the nodes of a dict,
+    and for each node invokes a callback function.\footnote{In most cases,
+    it is more convenient and preferable to 
+    iterate over the dict using explicit calls to {\tt dict_first}
+    and {\tt dict_next}.}
+    The second argument is a {\it context pointer\/} which can have any value.
+    The third argument of
+    \verb|dict_process| shall be a pointer to a function which is compatible
+    with the specified type. If the dict contains one or more nodes,
+    then the function is invoked once for each node, in order from first
+    to last. On each invocation, the first argument of the callback is a
+    pointer to the dict; the second argument is a pointer to a node, called
+    the {\it subject node}; and the third argument repeats the context pointer
+    value that was originally passed to \verb|dict_process|.
+
+    The callback function may delete the subject node by, for instance, calling
+    \verb|dict_delete|. It may insert new nodes into the dictionary;
+    however, if such an insertion causes the subject node to acquire
+    a new successor, it is implementation-defined whether upon returning
+    from the callback function, the traversal shall continue with the
+    new successor, or with the original successor.
+    
+    The callback function, and any function invoked from the callback
+    function, shall not destroy the dictionary or make any modifications
+    other than the insertion of new nodes, or the deletion of the 
+    subject node.
+
+    The callback function may recursively invoke \verb|dict_process| for the
+    same dictionary or for a different dictionary; the callback invocations arising out of
+    the nested call inherit all of the restrictions of the outer callback in
+    addition to being subject to the usual restrictions.\footnote{This means,
+    for instance, that if two callbacks are in progress for different
+    subject nodes from the same dictionary, the inner callback may not delete
+    its subject node, because it inherits the restriction that the only
+    permitted deletion is the outer callback's subject node.}
+
+    The callback function may freely operate on a different dictionary,
+    subject to any inherited restrictions.
+
+\subsubsection{The {\tt dict_load_begin} function}
+
+    \label{section:dict_load_begin}
+    \indexfunc{dict_load_begin}
+
+    \synopsis
+    \begin{verbatim}
+    void dict_load_begin(dict_load_t *, dict_t *);\end{verbatim}
+
+    \constraints
+    The dictionary specified by the second argument is empty.
+
+    \description
+    The \verb|dict_load_begin| function prepares a context object
+    for the task of constructing the contents of a dictionary  out of
+    a sequence of elements which is already sorted according to the
+    sorting function of the dictionary.\footnote{This process is more efficient 
+    than inserting all of the elements into a dictionary using {\tt dict_insert}. 
+    In the reference implementation, this process runs in linear time, or $O(n)$
+    whereas construction by repeated insertions runs in $O(n\log n)$ time.}
+    The actual construction is performed
+    by zero or more calls to \verb|dict_load_next| and is finalized by
+    \verb|dict_load_end|.
+
+    The \verb|dict_load_begin| function is said to bind the dictionary
+    and context object together; the only way to unbind the two 
+    is by calling \verb|dict_load_end| on the context object.
+
+    The program shall not manipulate a dictionary that is bound to
+    a context object, other than by calling \verb|dict_load_next|.
+
+    The program shall not attempt to bind a dictionary to more than one context
+    object simultaneously, or a context object to more than one dictionary
+    simultaneously.
+
+\subsubsection{The {\tt dict_load_next} function}
+
+    \label{section:dict_load_next}
+    \indexfunc{dict_load_next}
+
+    \synopsis
+    \begin{verbatim}
+    void dict_load_next(dict_load_t *, dnode_t *, const void *);\end{verbatim}
+
+    \constraints
+    The node pointed at by the second argument is not an occupant of
+    any dictionary. The key specified by the third argument is greater
+    than or equal to all keys specified in previous calls to 
+    \verb|dict_load_next| in the context of the same construction,
+    according to the comparison function of the dictionary that is
+    being constructed. That is to say, successive calls specify monotonically
+    increasing keys.
+    The dictionary is not full.
+
+    \description
+    The \verb|dict_load_next| function continues the construction of a
+    dictionary from an ordered list of elements by specifying the next
+    node in the sequence, along with its key. After this call, the node
+    is considered to be inserted into the dictionary as if by
+    \verb|dict_insert|.
+
+\subsubsection{The {\tt dict_load_end} function}
+
+    \label{section:dict_load_end}
+    \indexfunc{dict_load_end}
+
+    \synopsis
+    \begin{verbatim}
+    void dict_load_end(dict_load_t *);\end{verbatim}
+
+    \description
+    The \verb|dict_load_end| function finalizes the construction of
+    a dictionary from a ordered sequence.  It breaks the binding between
+    the \verb|dict_load_t| context object and the dictionary.
+
+\subsection{Implementation}
+
+TODO
+
+\section{Exception component}
+\label{section:exception_component}
+\index{Exception}
+
+The Exception component provides distributed error handling in the form of
+exceptions, behind an interface designed to be implementable using only the
+portable features of standard C. The features of this interface are:
+\begin{itemize}
+\item the ability to set up nested try-catch regions which declare specific
+exceptions that they can handle;
+\item grouped exceptions, allowing handlers to catch specific exceptions,
+or any exception within a group;
+\item the ability to designate a function that is called in the event
+that an exception is thrown that has no handler.
+\item a mechanism for releasing resources acquired by code that is terminated
+by an exception;
+\item the ability to pass dynamically allocated data from the throw site to the
+catch site.
+\end{itemize}
+
+An exception is simply a means of returning to a prior place in the program's
+execution. The ANSI C language provides crude, but portable, exception handling
+consisting of the \verb|jmp_buf| type, the \verb|setjmp| macro and the
+\verb|longjmp| function. The Kazlib Exception component can be implemented in
+terms of these primitives. The constraint to implementability in standard C
+leads to a number of concessions:
+\begin{itemize}
+\item A program can leave cleanup regions and try-catch regions by improper
+means, such as using \verb|goto|, \verb|return| or \verb|break|. This is
+difficult to diagnose, and is simply documented as undefined behavior.
+There is no support in the standard language for designating code that is
+executed whenever a statement block terminates by any means.
+\item For the same reason, the exception handling interface described here
+has an explicit mechanism for deallocation of resources associated with
+statement blocks that are terminated by exceptions. This interface is
+not as convenient as language support for automatic cleanup. Correct
+management of temporary dynamic resources using this interface requires
+programmer discipline.
+\item The requirement to be able to use \verb|setjmp| to save a context
+to be later returned to during exception processing brings in restrictions
+related to non-volatile objects. If non-volatile objects are modified 
+between the time an exception handling region is initiated and the time
+an exception is caught in the region, these objects have indeterminate
+values.\footnote{This liberty in ANSI C allows compiler
+or library writers to implement {\tt setjmp} as a simple mechanism that
+takes a snapshot of the machine context. Objects that are optimized into
+special storage---such as registers---and whose values change since the
+context saving operation will be clobbered when the context is restored
+by {\tt longjmp}.} 
+\end{itemize}
+
+\subsection{Interface}
+
+\subsubsection{The {\tt except.h} header}
+
+Each C or C++ translation unit that is to use the functionality of the Exception
+component shall include the header \verb|except.h|. This header shall
+contain declarations of types and external functions, and definitions of
+macros.  The following typedef names shall be
+defined:\index{Exception!typedef names}
+\begin{verbatim}
+    except_id_t
+    except_t
+\end{verbatim}
+The following external function names shall be declared:
+\index{Exception!function names}\index{functions!defined by Exception}
+\begin{verbatim}
+    except_init                 except_group   
+    except_deinit               except_message 
+    except_rethrow              except_data    
+    except_throw                except_take_data
+    except_throwd               except_set_allocator
+    except_throwf               except_alloc
+    except_unhandled_catcher    except_free
+    except_code            
+\end{verbatim}
+The following preprocessor symbols shall be
+defined: \index{Exception!macro names}\index{macros!defined by Exception}
+\indexmacro{XCEPT_H}
+\begin{verbatim}
+    XCEPT_H                     except_cleanup_pop
+    XCEPT_GROUP_ANY             except_checked_cleanup_pop
+    XCEPT_CODE_ANY              except_try_push
+    XCEPT_BAD_ALLOC             except_try_pop
+    except_cleanup_push
+\end{verbatim}
+Finally, these two enum constants are defined:
+\begin{verbatim}
+    except_no_call
+    except_call
+\end{verbatim}
+\index{symbols!reserved by Exception}\index{Exception!reserved symbols} Macro
+identifiers which begin with the upper-case prefix \verb|XCEPT|\footnote{The
+prefix {\tt XCEPT} is used rather than {\tt EXCEPT} because ISO 9899 reserves
+preprocessor symbols beginning with {\tt E} followed by a digit or
+capital letter for future extensions to the {\tt <errno.h>} header.}
+are reserved for future extensions to the \verb|except.h|
+header, as are names in the ordinary and tag namespaces which begin with
+\verb|except_|. External names which begin with \verb|except_| are reserved by
+the Kazlib library regardless of what headers are included.
+
+\subsubsection{The {\tt except_id_t} type}
+
+\label{section:except_id_t}
+\indextype{except_id_t}
+\indexmacro{XCEPT_GROUP_ANY}
+\indexmacro{XCEPT_CODE_ANY}
+The type \verb|except_id_t| is an aggregate consisting of two unsigned long
+values which represent an {\it exception group\/} and {\it exception code},
+respectively, in that order.\footnote{Thus, the program may initialize
+an {\tt except_id_t} object using two brace-enclosed initializers which
+specify the group and code.} An exception group is a value which identifies a
+group of related exceptions. An exception code is a value which identifies a
+specific exception uniquely within a group.  The codes are assigned by the
+program designer. The Exception component reserves only the group and code
+values of zero, which, when used to specify a catch, match any value.
+
+The preprocessor symbols \verb|XCEPT_GROUP_ANY| and
+\verb|XCEPT_CODE_ANY| each expand to a constant integral expression having the
+value zero.  These symbols are intended, in a catch specification, to clearly
+convey that any exception or any group is being caught.
+
+The preprocessor symbol \verb|XCEPT_BAD_ALLOC| expands to an integral constant
+expression having the value 1. This symbol is intended to represent the
+standard exception group for failed memory allocations.
+(See section \ref{section:except_throwf}).
+
+The exception groups from 1 to 15 are reserved for implementation use.
+
+\subsubsection{The {\tt except_t} type}
+
+\indextype{except_t}
+An object of type \verb|except_t| keeps track of all of the information that is
+passed when an exception is thrown, and is known as an {\it exception
+descriptor}.  The type is opaque, hence the program shall manipulate this type
+using only the interface functions provided.
+
+\subsubsection{The {\tt except_init} function}
+
+    \indexfunc{except_init}
+
+    \synopsis
+    \begin{verbatim}
+    int except_init(void);\end{verbatim}
+
+    \description
+    The \verb|except_init| function allocates resources needed by the
+    Exception component.  Before using any of the other exception interface
+    functions or macros, the program shall perform at least one successful call
+    to \verb|except_init|.
+
+    If the initialization succeeds, \verb|except_init| returns 1. Otherwise
+    it returns 0.
+
+    The \verb|except_init| function may be called more than once. After a
+    successful call, every subsequent call shall be successful up to an
+    implementation-defined maximum number of repetitions, which shall be at least
+    as large as the \verb|INT_MAX| from \verb|limits.h|. \footnote{
+    The intent is to support, but not enforce, a style of global initialization
+    whereby each module which requires the use of another module calls its
+    initialization function from its own initialization function. Only the
+    first such call performs the initialization of the module; subsequent calls
+    merely increment a counter. During deinitialization, the counter is
+    decremented and cleanup takes place when the counter reaches zero.}
+
+\subsubsection{The {\tt except_deinit} function}
+
+    \indexfunc{except_deinit}
+
+    \synopsis
+    \begin{verbatim}
+    void except_deinit(void);\end{verbatim}
+
+    \description
+    The \verb|except_deinit| function releases the resources 
+    that were allocated by \verb|except_init|.
+
+    For the resource deallocation to actually take place, the
+    \verb|except_deinit| must be called as many times as the
+    number of times \verb|except_init| was successfully called.
+
+    If \verb|except_deinit| is called more times than \verb|except_init| is
+    successfully called, the behavior is undefined.
+
+\subsubsection{The {\tt except_rethrow} function}
+
+    \indexfunc{except_rethrow}
+
+    \synopsis
+    \begin{verbatim}
+    void except_rethrow(except_t *);\end{verbatim}
+    
+    \description
+    The rethrow function is used to rethrow a caught exception.  The argument
+    shall not be null. An exception shall not be rethrown from outside of the
+    {\it try-catch region\/} in which it was caught. An exception shall not be
+    rethrown from a try-catch region other than the one in which it was caught.
+    It shall not be rethrown from a try-catch or cleanup region enclosed within
+    the one in which it was caught.
+
+    When an exception is rethrown, the search for a handler does not begin with
+    the region in which the exception was caught. Instead, this region is
+    terminated, and the search continues with the enclosing one, if one
+    exists.
+
+\subsubsection{The {\tt except_throw} function}
+
+    \indexfunc{except_throw}
+
+    \synopsis
+    \begin{verbatim}
+    void except_throw(long, long, const char *);\end{verbatim}
+
+    \constraints
+    The first two arguments specify the exception group and code,
+    respectively. Neither of these arguments shall be zero.
+
+    \description
+    The \verb|except_throw| function causes an exception to be thrown.
+
+    If the throw takes place in a try-catch region where an exception
+    was just caught, this original exception is considered handled. In
+    this case, the new exception is still eligible for handling by the
+    same try-catch region.
+
+    The third argument points to the first character of a string
+    which becomes the {\it exception message}. Because the throwing of
+    the exception may cause the current statement block to terminate,
+    this string data shall be non-local. It may be a string literal, since the
+    implementation shall not modify the message, or it may be an ordinary
+    object of static duration. If it is dynamic data, it becomes the handler's
+    responsibility to extract the message from the caught exception and
+    free the data.\footnote{The programmer should consider using 
+    {\tt except_throwd} to pass arbitrary dynamic data from the throw
+    site to the try-catch region.}
+
+    The \verb|except_throw| function does not return. The implementation
+    searches for a suitable try-catch region starting with the one
+    initiated by the most recent \verb|except_try_push|. If there
+    is no enclosing region, the search fails.  Otherwise if a match is found,
+    execution continues at the start of the target try-catch region, appearing
+    to be a second return from \verb|except_try_push| distinguished by a non-null
+    value of the \verb|except_t *| object.
+
+    If no match is found during exception processing, the exception is
+    handled internally by the implementation. The implementation then
+    calls the currently registered function for catching unhandled
+    exceptions (see section \ref{section:except_unhandled_catcher}).
+
+    The default catcher for unhandled exceptions shall terminate the program
+    with a diagnostic which identifies the code, group and exception message.
+
+    During the search for an exception handler, cleanup handlers may be
+    encountered. They are removed from the inside out and called with
+    their registered arguments. This process is called {\it unwinding}.
+    \index{unwinding}
+
+\subsubsection{The {\tt except_throwd} function}
+
+    \indexfunc{except_throwd}
+
+    \synopsis
+    \begin{verbatim}
+    void except_throwd(long, long, const char *, void *);\end{verbatim}
+
+    \constraints
+    The first two arguments specify the exception group and code,
+    respectively. Neither of these arguments shall be zero.
+
+    \description
+    The \verb|except_throwd| function is the same as \verb|except_throw| in
+    every respect except that it has an additional \verb|void *| parameter. A
+    null argument may be used for this parameter, or it may be any valid
+    pointer value.
+
+    When the exception is handled, and the handler does not remove this pointer
+    using \verb|except_take_data| then the implementation shall automatically
+    invoke the function \verb|except_free| on this pointer.
+
+\subsubsection{The {\tt except_throwf} function}
+
+    \indexfunc{except_throwf}
+    \label{section:except_throwf}
+
+    \synopsis
+    \begin{verbatim}
+    void except_throwf(long, long, const char *, ...);\end{verbatim}
+
+    \constraints
+    The first two arguments specify the exception group and code,
+    respectively. Neither of these arguments shall be zero.
+
+    \description
+
+    This function is almost exactly the same as \verb|except_throw|
+    except that the exception message is not directly specified.
+    Instead, the \verb|char *| argument specifies a format string which may be
+    followed by trailing arguments. The format string and trailing arguments
+    are interpreted as the format string and arguments of the standard C
+    function \verb|printf| and are subject to the same requirements.
+    
+    The format string is interpreted, and the results of formatting are placed into
+    buffer provided by the implementation.  The implementation shall provide
+    space for at least 1024 bytes of storage for the result of the formatting,
+    including the null terminator byte. If the formatting requires more space
+    than the implementation provides, the behavior is undefined.
+
+    The results of the formatted print shall become the exception message
+    of the thrown exception.
+
+    If the implementation is unable to allocate resources for the formatted
+    message, it shall throw a code 1 exception having an unspecified code in
+    group \verb|XCEPT_BAD_ALLOC| with an implementation-defined message.
+    (See section \ref{section:except_id_t}).
+
+\subsubsection{The {\tt except_unhandled_catcher} function}
+
+    \label{section:except_unhandled_catcher}
+    \indexfunc{except_unhandled_catcher}
+
+    \synopsis
+    \begin{verbatim}
+    void (*except_unhandled_catcher(void (*)(except_t *)))
+            (except_t *);\end{verbatim}
+
+    \description
+    The \verb|except_unhandled_catcher| function installs a new
+    function for catching unhandled exceptions. The argument is a 
+    pointer to a catching function that returns nothing, and accepts a pointer
+    of type \verb|except_t *|.  A pointer to the previously installed
+    catching function is returned. If the program did not previously
+    install a catching function, then a pointer to the default catching
+    function is returned. The program may retain this pointer and
+    use it to reinstall the default function.
+
+    A function for catching unhandled exceptions should not return. If it
+    returns, the implementation shall terminate the program with a diagnostic.
+
+\subsubsection{The {\tt except_code} function}
+
+    \indexfunc{except_code}
+
+    \synopsis
+    \begin{verbatim}
+    unsigned long except_code(except_t *);\end{verbatim}
+
+    \description
+    The \verb|except_code| is an accessor function which returns the
+    exception code of the given exception descriptor.
+
+\subsubsection{The {\tt except_group} function}
+
+    \indexfunc{except_group}
+
+    \synopsis
+    \begin{verbatim}
+    unsigned long except_group(except_t *);\end{verbatim}
+
+    \description
+    The \verb|except_group| is an accessor function which returns the
+    exception group of the given exception descriptor.
+
+\subsubsection{The {\tt except_message} function}
+
+    \indexfunc{except_message}
+
+    \synopsis
+    \begin{verbatim}
+    const char *except_message(except_t *);\end{verbatim}
+
+    \description
+    The \verb|except_group| is an accessor function which returns 
+    a pointer to the string of text that was specified when the
+    exception was thrown (the exception message).
+
+\subsubsection{The {\tt except_data} function}
+
+    \indexfunc{except_data}
+
+    \synopsis
+    \begin{verbatim}
+    void *except_data(except_t *);\end{verbatim}
+
+    \description
+    The \verb|except_group| returns the data pointer that
+    was specified in the \verb|except_throwd| call.
+    If the exception was not thrown by \verb|except_throwd|
+    the return value is unspecified.
+
+
+\subsubsection{The {\tt except_take_data} function}
+
+    \indexfunc{except_take_data}
+
+    \synopsis
+    \begin{verbatim}
+    void *except_take_data(except_t *);\end{verbatim}
+
+    \description
+    The \verb|except_take_data| returns the data pointer that
+    was specified in the \verb|except_throwd| call, and
+    updates the exception descriptor so that the pointer is
+    set to null.
+
+    If the exception was not thrown by \verb|except_throwd|
+    the result is unspecified.
+
+\subsubsection{The {\tt except_cleanup_push} macro}
+
+    \indexmacro{except_cleanup_push}
+
+    \synopsis
+    \begin{verbatim}
+    void except_cleanup_push(void (*)(void *), void *);\end{verbatim}
+
+    \description
+    The call to \verb|except_cleanup_push| shall be matched with a call to
+    \verb|except_cleanup_pop| which must occur in the same statement block at
+    the same level of nesting.\footnote{This requirement allows an implementation
+    to provide an {\tt except_cleanup_push} macro which opens up a statement
+    block and a {\tt except_cleanup_pop} which closes the statement block.
+    The space for the registered pointers can then be efficiently allocated
+    from automatic storage.}
+
+    The \verb|except_cleanup_push| macro registers a cleanup handler that will
+    be called if an exception subsequently occurs before the matching
+    \verb|except_cleanup_pop| is executed, and is not intercepted and handled by
+    a try-catch region that is nested between the two.
+
+    The first argument to \verb|except_cleanup_push| is a pointer
+    to the cleanup handler, a function that returns nothing and takes
+    a single argument of type \verb|void *|. The second argument
+    is a \verb|void *| value that is registered along with the handler.
+    This value is what is passed to the registered handler, should it
+    be called.
+
+    Cleanup handlers are called in the reverse order of their nesting: inner
+    handlers are called before outer handlers.
+
+    The program shall not leave the cleanup region between the call to the macro
+    \verb|except_cleanup_push| and the matching call to
+    \verb|except_cleanup_pop| by means other than throwing an exception, or
+    calling \verb|except_cleanup_pop|.
+
+    Within the call to the cleanup handler, it is possible that new exceptions
+    may happen.  Such exceptions must be handled before the cleanup handler
+    terminates. If the call to the cleanup handler is terminated by an
+    exception, the behavior is undefined.\footnote{The exception which triggered
+    the cleanup is not yet caught; thus the program would be effectively trying
+    to replace an exception with one that isn't in a well-defined state.}
+
+\subsubsection{The {\tt except_cleanup_pop} macro}
+
+    \indexmacro{except_cleanup_pop}
+    \label{section:except_cleanup_pop}
+
+    \synopsis
+    \begin{verbatim}
+    void except_cleanup_pop(int);\end{verbatim}
+
+    \description
+    A call to the \verb|except_cleanup_pop| macro shall match each
+    call to \verb|except_cleanup_push| which shall be in the
+    same statement block at the same nesting level.  It shall
+    match the most recent such a call that is not matched
+    by a previous \verb|except_cleanup_pop| at the same level.
+
+    This macro causes the registered cleanup handler to be removed.  If, and
+    only if the argument is other than zero, the cleanup handler is called.
+    In that case, the registered context pointer is passed to the cleanup
+    handler.
+
+    \indexenum{except_no_call}
+    \indexenum{except_call}
+    The enumeration constants \verb|except_no_call| and \verb|except_call|
+    may be used as arguments to this function instead of
+    the equivalent constants \verb|0| and \verb|1|.
+
+    The program shall not leave the region between the call to the macro
+    \verb|except_cleanup_push| and the matching call to
+    \verb|except_cleanup_pop| other than by throwing an exception, or
+    by executing the \verb|except_cleanup_pop|.
+
+\subsubsection{The {\tt except_checked_cleanup_pop} macro}
+
+    \indexmacro{except_checked_cleanup_pop}
+
+    \synopsis
+    \begin{verbatim}
+    void except_checked_cleanup_pop(void (*)(void *), int);\end{verbatim}
+
+    \constraints
+    The first pointer-to-function argument shall match the pointer value that
+    was registered by the matching \verb|except_cleanup_push| macro.
+
+    \description
+    The \verb|except_checked_cleanup_pop| macro may be used as an alternative to
+    \verb|except_cleanup_pop|. In verification mode, the constraint serves to
+    provide additional safety by making an explicit declaration regarding which
+    handler is being called (or ignored, as the case may be).
+
+    The program shall not leave the region between the call to the macro
+    \verb|except_cleanup_push| and the call to
+    \verb|except_checked_cleanup_pop| by means other than throwing an
+    exception, or executing the latter macro.
+
+\subsubsection{The {\tt except_try_push} macro}
+
+    \indexmacro{except_try_push}
+    \label{section:except_try_push}
+
+    \synopsis
+    \begin{verbatim}
+    void except_try_push(const except_id_t [],
+            size_t, except_t **);\end{verbatim}
+
+    \description
+    The \verb|except_try_push| marks the beginning of a try-catch region
+    of the program. It must be matched by a \verb|except_try_pop| written in
+    the same statement block at the same level of nesting, which
+    terminates the try-catch region. Regions may be nested.
+
+    The program shall not leave a try-catch region other than by throwing
+    an exception or by executing the \verb|except_try_pop|.\footnote{Thus,
+    leaving the try-catch region using {\tt goto}, {\tt return},
+    {\tt break} or {\tt continue} leads to undefined behavior.}
+
+    The first argument is a pointer to the first element of an array of
+    \verb|except_id_t| objects, the number of elements of which is specified by
+    the second argument. The array specifies which exceptions are caught.
+    The implementation shall treat this array as read-only.\footnote{Thus,
+    the program may allocate the array in static storage.}
+
+    The third argument of \verb|except_try_push| shall point to an object
+    of type \verb|except_t *|. After the call to \verb|except_try_push|,
+    the program shall inspect the value of this object. A null value indicates
+    that no exception has been thrown. A non-null value indicates that an
+    exception was thrown, and is now caught. In other words, when an exception
+    is caught by a try-catch region, then control passes from the throw site
+    back to the first statement after the \verb|except_try_push| statement of
+    the try-catch region. This case is distinguished from an ordinary return by
+    the non-null value of the pointer object that was specified by the third
+    argument of the earlier call to \verb|except_try_push|.
+
+    An exception is considered handled if it is caught in a try-catch region
+    which subsequently terminates by executing its \verb|except_try_pop| or by
+    throwing another exception.  When an exception is considered handled, any
+    dynamic data that was associated with that exception is
+    freed.\footnote{Dynamic data may be explicitly associated with an exception
+    using {\tt except_throwd}. Other types of throw may associate unspecified
+    dynamic data.} It's possible for more than one exception to be active
+    at once. During the processing of one exception, a try-catch region
+    which catches the exception may execute a nested try-catch region
+    in which independent exception processing takes place. Provided that 
+    no exception escapes from the inner try-catch region, the original
+    exception remains pending. But if an exception escapes from the inner
+    region, it causes the original exception to be handled.\footnote{Thus, a
+    given try-catch region cannot catch multiple exceptions concurrently.}
+   
+    The caught exception may be rethrown by calling \verb|except_rethrow|,
+    specifying the the value of the caught exception descriptor as the
+    argument.  Rethrowing a caught exception causes the innermost try-catch
+    region to terminate, but the exception is not considered handled. The
+    search for a handler continues with the second most enclosing region.
+
+    Throwing a new exception during the handling of a caught exception may
+    cause the {\it same\/} try-catch region to catch that exception; the
+    try-catch region is not terminated until it is determined that it doesn't
+    catch the new exception.
+
+    Each entry in the array of \verb|except_id_t| objects specifies what
+    exceptions are caught by the try-catch region. When an exception is
+    thrown, the implementation searches for the inner-most try-catch region
+    which has at least one match for the thrown exception in its catch
+    specification array. 
+
+    A match occurs when a specification exactly matches the group and code of
+    the thrown exception. If a catch specification is for group 0, then it
+    matches any group. If a catch specification is for code 0, then it matches
+    any exception code. A catch specification of group 0 and code 0 catches all
+    exceptions.
+
+    Non-volatile automatic variables that are local to the function containing
+    the try-catch region, and that are modified after \verb|except_try_push|
+    begins the try-catch region have indeterminate values when an exception is
+    caught.
+
+    Once a caught exception is handled or re-thrown, the value of the
+    \verb|except_t *| pointer which referenced it becomes indeterminate.
+    If a re-thrown exception is caught again, the implementation shall
+    produce a valid \verb|except_t *| pointer.
+
+    \example
+    The following example illustrates the use of \verb|except_try_push| and
+    related macros and functions.
+    \begin{verbatim}
+    #include <stdlib.h>
+    #include <assert.h>
+    #include "except.h"
+
+    #define MY_GROUP 42
+    #define MY_CODE   1
+
+    static void func_that_throws(void)
+    {
+        except_throw(MY_GROUP, MY_CODE, "this is an exception");        
+    }
+
+    static void func_that_cleans_up(void)
+    {
+        void *local_data = malloc(10);
+
+        except_cleanup_push(free, local_data);
+        func_that_throws();
+        except_checked_cleanup_pop(free, except_call);
+    }
+
+    void func_that_catches(void)
+    {
+        /* catch specification */
+        static const except_id_t catch_spec[] = {
+            { MY_GROUP, XCEPT_CODE_ANY }
+        };
+        /* exception handle */
+        except_t *exc;
+
+        except_try_push(catch_spec, 1, &exc);
+
+        /*
+         * Start of try-catch region: when exception is 
+         * thrown, control returns here.
+         */
+
+        if (exc == 0) {
+            /* try code that may throw an exception */
+
+            func_that_cleans_up();
+        } else {
+            /* handle exception that was thrown */
+
+            assert (except_group(exc) == MY_GROUP);
+            printf("exception caught: %s %ld %ld\n",
+                except_message(exc),
+                except_group(exc), except_code(exc));
+
+            goto terminate; /* ERROR! jumping out of try-catch */
+        }
+
+        /* end of try-catch region */
+
+        except_try_pop();
+    terminate:
+        ;
+    }
+    \end{verbatim}
+    In this example, the function \verb|func_that_catches| is intended to be
+    called first.  It sets up a try-catch region which traps exceptions having
+    the group identification \verb|MY_GROUP| (or 42). Any code within that
+    group is caught because the code catch was specified as
+    \verb|XCEPT_CODE_ANY|. When the \verb|except_try_push| macro is executed,
+    it sets the value of \verb|exc| to null. Then \verb|func_that_cleans_up| is
+    called, which throws an exception in the \verb|MY_GROUP| group. This
+    exception is caught, so control resumes at the top of the try-catch region,
+    with \verb|exc| set to a non-null value.  Thus the else clause of the if
+    statement is now executed.  The handling code simply prints the exception
+    message on standard output, as well as the numeric group and code.  The
+    subsequent goto statement demonstrates a serious programming error.
+
+    The \verb|func_that_cleans_up| function illustrates the use of cleanup
+    regions. Dynamic memory is allocated which must not be allowed to leak
+    when an exception is thrown, so a cleanup handler is set up to free the
+    memory in that event. The standard C function \verb|free| happens to have,
+    the right type signature and semantics that it can be used directly as a
+    cleanup handler.   Should no exception be thrown, the cleanup pop macro
+    will perform the call to the cleanup handler, because it is invoked with
+    argument \verb|except_call|.
+
+\subsubsection{The {\tt except_try_pop} macro}
+
+    \indexmacro{except_try_pop}
+
+    \synopsis
+    \begin{verbatim}
+    void except_try_pop(void);\end{verbatim}
+
+    \description
+
+    The \verb|except_try_pop| macro terminates a try-catch region. It must
+    match a previous \verb|except_try_push| macro in the same statement
+    block at the same level of nesting which is not already matched by an
+    earlier \verb|except_try_pop|.
+
+\subsubsection{The {\tt except_set_allocator} function}
+
+    \indexfunc{except_set_allocator}
+    \label{section:except_set_allocator}
+
+    \synopsis
+    \begin{verbatim}
+    void except_set_allocator(void *(*)(size_t), void (*)(void *));\end{verbatim}
+
+    \description
+    The \verb|except_set_allocator| function installs a pair of allocator
+    routines that will be used by the Exception component for future allocation
+    and deallocation requests. 
+
+    The first argument points to a function that resembles the standard C
+    \verb|malloc| in type and semantics. The second argument points to a
+    function that similarly resembles the standard C function \verb|free|.
+
+    The default allocators are \verb|malloc| and \verb|free|. 
+    The call
+    \begin{verbatim}
+    except_set_allocator(malloc, free);
+    \end{verbatim}
+    may be used to restore these default allocator functions.
+
+    The program shall not call \verb|except_set_allocator| if an exception
+    was thrown and has not yet been handled.\footnote{Doing so could, for example,
+    create a mismatch whereby a pointer to data allocated with the previously installed
+    allocator function would be passed to the new deallocator function.}
+
+    The allocator function shall create a unique object consisting of at least
+    as many bytes of storage as indicated by the value of the argument.
+    The pointer returned shall be suitably aligned to represent an object
+    of any type. If insufficient resources exist, the pointer returned shall be
+    null. Requesting an object of zero size may produce a unique pointer
+    that shall be acceptable to the deallocator function, or a null pointer.
+
+    The deallocator function shall be capable of destroying objects created
+    by the corresponding allocator function. Passing a null pointer to the
+    deallocator shall have no effect.
+
+\subsubsection{The {\tt except_alloc} function}
+
+    \indexfunc{except_alloc}
+
+    \synopsis
+    \begin{verbatim}
+    void *except_alloc(size_t);\end{verbatim}
+
+    \description
+    The \verb|except_alloc| function allocates memory using the default
+    memory allocator or one installed by the program.
+    (See section \ref{section:except_set_allocator}).
+
+    If the allocation succeeds, a non-null pointer to the allocated object is
+    returned.
+
+    If the allocator indicates failure by returning a null pointer,
+    then instead of returning, \verb|except_alloc| throws exception code 1 
+    in the group \verb|XCEPT_BAD_ALLOC| (See section \ref{section:except_id_t}).
+
+    If a zero size request is specified, then an exception is thrown or
+    a non-null pointer is returned, depending on the treatment of such
+    requests by the underlying allocator.
+
+\subsubsection{The {\tt except_free} function}
+
+    \indexfunc{except_free}
+
+    \synopsis
+    \begin{verbatim}
+    void *except_free(void *);\end{verbatim}
+
+    \description
+
+    The \verb|except_free| function releases memory that was allocated
+    using \verb|except_alloc|.  The deallocation is performed using the
+    default allocator or one installed by the program.
+
+    If an object is allocated by \verb|except_alloc|, then a 
+    different allocator is installed, and the object is freed using
+    \verb|except_free|, the behavior is undefined.
+
+\subsection{Implementation}
+\index{Exception component!reference implementation}
+
+Described here is a reference implementation of the exception handling
+interface that is covered in section \ref{section:exception_component}
+The reference implementation requires only a conforming ANSI C implementation.
+In particular, the actual mechanism for passing control from an exception throw
+to a catch handler is based on the standard C \verb|setjmp| macro and
+\verb|longjmp| function.
+
+\subsubsection{Overview}
+
+The core structure in the exception handling implementation is a stack that is
+composed of a mixture of two types of nodes: cleanup nodes and catch nodes.
+When an exception is thrown, the stack nodes are popped and processed starting
+with the topmost one. 
+
+The nodes are efficiently allocated in automatic storage by the macros
+\verb|except_cleanup_push| and \verb|except_try_push|. These macros
+open up a new statement block and declare the node information in automatic
+storage.  These objects are then pushed onto the stack. The corresponding macros
+\verb|except_cleanup_pop| and \verb|except_try_pop| pop the node off the stack
+and close the statement block.
+
+An static variable keeps track of the stack top. In the multi-threaded variant
+of the code which is based on the POSIX threading interface, there is a
+thread-specific stack top created using the thread-specific function
+pthread_key_create. Using global variables is a compromise that simplifies the
+interface; the throw functions simply ``know'' where the thread's exception
+stack is, so the context information doesn't have to be passed around.
+
+\subsubsection{Stack nodes}
+
+A node in the exception handling stack contains a pointer to the next
+node below, followed by a type field and a union which together keep
+track of the appropriate type-specific data:
+\begin{verbatim}
+    enum except_stacktype {
+        XCEPT_CLEANUP, XCEPT_CATCHER
+    };
+
+    struct except_stacknode {
+        struct except_stacknode *except_down;
+        enum except_stacktype except_type;
+        union {
+            struct except_catch *except_catcher;       
+            struct except_cleanup *except_cleanup;
+        } except_info;
+    };
+\end{verbatim}
+The union overlaps pointers to structures instead of structures in order to
+save space: there is a disparity in size between a cleanup node and a catch
+node, so making them both use the same amount of space would be wasteful.
+The space saving comes at a price, because the pointers themselves take up
+extra space and time is spent initializing them. Some casting trickery
+could be used to create a stack having two different kinds of structures
+without the use of unions.
+
+\paragraph{Cleanup nodes}
+
+Cleanup nodes act as placeholders for a pointer to a cleanup handler function
+and a context pointer to be passed to that function. The type-dependent
+component of the cleanup node is declared like this:
+\begin{verbatim}
+    struct except_cleanup {
+        void (*except_func)(void *);
+        void *except_context;
+    };
+\end{verbatim}
+The cleanup handler is invoked when the node is popped during exception
+processing. A cleanup handler may also be invoked when the cleanup node is
+removed by executing \verb|except_cleanup_pop| or
+\verb|except_checked_cleanup_pop|. Whether or not this happens depends on the
+integer parameter that is documented in section
+\ref{section:except_cleanup_pop}.
+
+\paragraph{Catch nodes}
+
+The catch node structure is more complicated than the cleanup node.
+Its definition depends on two additional types, \verb|except_id_t|
+and \verb|except_t|, both of which also make play a role in the exception
+component's interface.
+\begin{verbatim}
+    typedef struct {
+        unsigned long except_group;
+        unsigned long except_code;
+    } except_id_t;
+
+    typedef struct {
+        except_id_t except_id;
+        const char *except_message;
+        void *except_dyndata;
+    } except_t;
+
+    struct except_catch {
+        const except_id_t *except_id;
+        size_t except_size;
+        except_t except_obj;
+        jmp_buf except_jmp;
+    };
+\end{verbatim}
+The \verb|except_id| member of the \verb|except_catch| structure is a pointer to the
+array of \verb|except_id_t| objects which specify what exceptions the node
+catches. The \verb|except_size| member specifies the number of elements in the array.
+Both of these values are derived directly from the arguments of the
+\verb|except_try_push| macro (see section \ref{section:except_try_push}). The
+\verb|except_obj| member provides storage for the caught exception. This member is
+the means by which the thrown exception is communicated to the try-catch region
+where it is caught. It contains the group and code identifiers, the exception
+message and, optionally, the pointer to arbitrary exception data.  The
+\verb|except_jmp| member is the standard C \verb|jmp_buf|---a place for saving the
+execution context so that it's possible to pass control, via \verb|longjmp|
+from the place where an exception is thrown to the place where it is caught.
+
+If, during the search for an exception handler, a catch node is encountered
+which matches the thrown exception, the node remains the stack.  The exception
+information is stored into into the node's \verb|except_obj| member and a
+\verb|longjmp| is executed to return to the try-catch region in which the node
+was allocated and pushed. Because the node is still on the stack, it's possible
+to throw another exception which is caught again by the same node.  When an
+exception is thus caught, control resumes just after the \verb|except_throw|
+which placed the node onto the stack. The pointer passed into \verb|except_throw|
+is updated  to point to the \verb|except_obj| member of the catch structure.
+The program can then use the portable accessor functions such as
+\verb|except_code| to gain information about the caught exception and handle it
+accordingly.
+
+\index{external names|see {functions}}
+\index{reference implementation|see {implementation}}
+\index{names|see {symbols}}
+\index{identifiers|see {symbols}}
+\index{structure names|see{tags}}
+\index{preprocessor symbols|see{macros}}
+\index{defines|see{macros}}
+\index{reserved symbols|see{symbols}}
+\index{symbols!preprocessor|see{macros}}
+\index{symbols!type names|see{typedefs}}
+\index{symbols!function names|see{functions}}
+\printindex 
+
+\end{document}
diff --git a/libutil/kazlib/drivers/dict-main.c b/libutil/kazlib/drivers/dict-main.c
new file mode 100644
index 0000000..08f2e7a
--- /dev/null
+++ b/libutil/kazlib/drivers/dict-main.c
@@ -0,0 +1,300 @@
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+typedef char input_t[256];
+
+static int tokenize(char *string, ...)
+{
+    char **tokptr; 
+    va_list arglist;
+    int tokcount = 0;
+
+    va_start(arglist, string);
+    tokptr = va_arg(arglist, char **);
+    while (tokptr) {
+	while (*string && isspace((unsigned char) *string))
+	    string++;
+	if (!*string)
+	    break;
+	*tokptr = string;
+	while (*string && !isspace((unsigned char) *string))
+	    string++;
+	tokptr = va_arg(arglist, char **);
+	tokcount++;
+	if (!*string)
+	    break;
+	*string++ = 0;
+    }
+    va_end(arglist);
+
+    return tokcount;
+}
+
+static int comparef(const void *key1, const void *key2)
+{
+    return strcmp(key1, key2);
+}
+
+static char *dupstring(char *str)
+{
+    int sz = strlen(str) + 1;
+    char *new = malloc(sz);
+    if (new)
+	memcpy(new, str, sz);
+    return new;
+}
+
+static dnode_t *new_node(void *c)
+{
+    static dnode_t few[5];
+    static int count;
+
+    if (count < 5)
+	return few + count++;
+
+    return NULL;
+}
+
+static void del_node(dnode_t *n, void *c)
+{
+}
+
+static int prompt = 0;
+
+static void construct(dict_t *d)
+{
+    input_t in;
+    int done = 0;
+    dict_load_t dl;
+    dnode_t *dn;
+    char *tok1, *tok2, *val;
+    const char *key;
+    char *help = 
+	"p                      turn prompt on\n"
+	"q                      finish construction\n"
+	"a <key> <val>          add new entry\n";
+
+    if (!dict_isempty(d))
+	puts("warning: dictionary not empty!");
+
+    dict_load_begin(&dl, d);
+
+    while (!done) {
+	if (prompt)
+	    putchar('>');
+	fflush(stdout);
+
+	if (!fgets(in, sizeof(input_t), stdin))
+	    break;
+
+	switch (in[0]) {
+	    case '?':
+		puts(help);
+		break;
+	    case 'p':
+		prompt = 1;
+		break;
+	    case 'q':
+		done = 1;
+		break;
+	    case 'a':
+		if (tokenize(in+1, &tok1, &tok2, (char **) 0) != 2) {
+		    puts("what?");
+		    break;
+		}
+		key = dupstring(tok1);
+		val = dupstring(tok2);
+		dn = dnode_create(val);
+
+		if (!key || !val || !dn) {
+		    puts("out of memory");
+		    free((void *) key);
+		    free(val);
+		    if (dn)
+			dnode_destroy(dn);
+		}
+
+		dict_load_next(&dl, dn, key);
+		break;
+	    default:
+		putchar('?');
+		putchar('\n');
+		break;
+	}
+    }
+
+    dict_load_end(&dl);
+}
+
+int main(void)
+{
+    input_t in;
+    dict_t darray[10];
+    dict_t *d = &darray[0];
+    dnode_t *dn;
+    int i;
+    char *tok1, *tok2, *val;
+    const char *key;
+
+    char *help =
+	"a <key> <val>          add value to dictionary\n"
+	"d <key>                delete value from dictionary\n"
+	"l <key>                lookup value in dictionary\n"
+	"( <key>                lookup lower bound\n"
+	") <key>                lookup upper bound\n"
+	"# <num>                switch to alternate dictionary (0-9)\n"
+	"j <num> <num>          merge two dictionaries\n"
+	"f                      free the whole dictionary\n"
+	"k                      allow duplicate keys\n"
+	"c                      show number of entries\n"
+	"t                      dump whole dictionary in sort order\n"
+	"m                      make dictionary out of sorted items\n"
+	"p                      turn prompt on\n"
+	"s                      switch to non-functioning allocator\n"
+	"q                      quit";
+
+    for (i = 0; i < sizeof darray / sizeof *darray; i++)
+	dict_init(&darray[i], DICTCOUNT_T_MAX, comparef);
+
+    for (;;) {
+	if (prompt)
+	    putchar('>');
+	fflush(stdout);
+
+	if (!fgets(in, sizeof(input_t), stdin))
+	    break;
+
+	switch(in[0]) {
+	    case '?':
+		puts(help);
+		break;
+	    case 'a':
+		if (tokenize(in+1, &tok1, &tok2, (char **) 0) != 2) {
+		    puts("what?");
+		    break;
+		}
+		key = dupstring(tok1);
+		val = dupstring(tok2);
+
+		if (!key || !val) {
+		    puts("out of memory");
+		    free((void *) key);
+		    free(val);
+		}
+
+		if (!dict_alloc_insert(d, key, val)) {
+		    puts("dict_alloc_insert failed");
+		    free((void *) key);
+		    free(val);
+		    break;
+		}
+		break;
+	    case 'd':
+		if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+		    puts("what?");
+		    break;
+		}
+		dn = dict_lookup(d, tok1);
+		if (!dn) {
+		    puts("dict_lookup failed");
+		    break;
+		}
+		val = dnode_get(dn);
+		key = dnode_getkey(dn);
+		dict_delete_free(d, dn);
+
+		free(val);
+		free((void *) key);
+		break;
+	    case 'f':
+		dict_free(d);
+		break;
+	    case 'l':
+	    case '(':
+	    case ')':
+		if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+		    puts("what?");
+		    break;
+		}
+		dn = 0;
+		switch (in[0]) {
+		case 'l':
+		    dn = dict_lookup(d, tok1);
+		    break;
+		case '(':
+		    dn = dict_lower_bound(d, tok1);
+		    break;
+		case ')':
+		    dn = dict_upper_bound(d, tok1);
+		    break;
+		}
+		if (!dn) {
+		    puts("lookup failed");
+		    break;
+		}
+		val = dnode_get(dn);
+		puts(val);
+		break;
+	    case 'm':
+		construct(d);
+		break;
+	    case 'k':
+		dict_allow_dupes(d);
+		break;
+	    case 'c':
+		printf("%lu\n", (unsigned long) dict_count(d));
+		break;
+	    case 't':
+		for (dn = dict_first(d); dn; dn = dict_next(d, dn)) {
+		    printf("%s\t%s\n", (char *) dnode_getkey(dn),
+			    (char *) dnode_get(dn));
+		}
+		break;
+	    case 'q':
+		exit(0);
+		break;
+	    case '\0':
+		break;
+	    case 'p':
+		prompt = 1;
+		break;
+	    case 's':
+		dict_set_allocator(d, new_node, del_node, NULL);
+		break;
+	    case '#':
+		if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+		    puts("what?");
+		    break;
+		} else {
+		    int dictnum = atoi(tok1);
+		    if (dictnum < 0 || dictnum > 9) {
+			puts("invalid number");
+			break;
+		    }
+		    d = &darray[dictnum];
+		}
+		break;
+	    case 'j':
+		if (tokenize(in+1, &tok1, &tok2, (char **) 0) != 2) {
+		    puts("what?");
+		    break;
+		} else {
+		    int dict1 = atoi(tok1), dict2 = atoi(tok2);
+		    if (dict1 < 0 || dict1 > 9 || dict2 < 0 || dict2 > 9) {
+			puts("invalid number");
+			break;
+		    }
+		    dict_merge(&darray[dict1], &darray[dict2]);
+		}
+		break;
+	    default:
+		putchar('?');
+		putchar('\n');
+		break;
+	}
+    }
+
+    return 0;
+}
diff --git a/libutil/kazlib/drivers/except-main.c b/libutil/kazlib/drivers/except-main.c
new file mode 100644
index 0000000..fdb64db
--- /dev/null
+++ b/libutil/kazlib/drivers/except-main.c
@@ -0,0 +1,57 @@
+#include <stdio.h>
+#include <ctype.h>
+
+static void cleanup(void *arg)
+{
+    printf("cleanup(\"%s\") called\n", (char *) arg);
+}
+
+static void bottom_level(void)
+{
+    char buf[256];
+    printf("throw exception? "); fflush(stdout);
+    fgets(buf, sizeof buf, stdin);
+
+    if (buf[0] >= 0 && toupper(buf[0]) == 'Y')
+	except_throw(1, 1, "nasty exception");
+}
+
+static void top_level(void)
+{
+    except_cleanup_push(cleanup, "argument");
+    bottom_level();
+    except_cleanup_pop(0);
+}
+
+int main(int argc, char **argv)
+{
+    static const except_id_t catch[] = { { 1, 1 }, { 1, 2 } };
+    except_t *ex;
+
+    /*
+     * Nested exception ``try blocks''
+     */
+
+    /* outer */
+    except_try_push(catch, 2, &ex);
+    if (!ex) {
+	/* inner */
+	except_try_push(catch, 2, &ex);
+	if (!ex) {
+	    top_level();
+	} else {
+	    /* inner catch */
+	    printf("caught exception (inner): \"%s\", s=%ld, c=%ld\n",
+		    except_message(ex), except_group(ex), except_code(ex));
+	    except_rethrow(ex);
+	}
+	except_try_pop();
+    } else {
+	/* outer catch */
+	printf("caught exception (outer): \"%s\", s=%ld, c=%ld\n",
+		except_message(ex), except_group(ex), except_code(ex));
+    }
+    except_try_pop();
+    except_throw(99, 99, "exception in main");
+    return 0;
+}
diff --git a/libutil/kazlib/drivers/hash-main.c b/libutil/kazlib/drivers/hash-main.c
new file mode 100644
index 0000000..0a08542
--- /dev/null
+++ b/libutil/kazlib/drivers/hash-main.c
@@ -0,0 +1,187 @@
+#include <stdio.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+typedef char input_t[256];
+
+static int tokenize(char *string, ...)
+{
+    char **tokptr; 
+    va_list arglist;
+    int tokcount = 0;
+
+    va_start(arglist, string);
+    tokptr = va_arg(arglist, char **);
+    while (tokptr) {
+	while (*string && isspace((unsigned char) *string))
+	    string++;
+	if (!*string)
+	    break;
+	*tokptr = string;
+	while (*string && !isspace((unsigned char) *string))
+	    string++;
+	tokptr = va_arg(arglist, char **);
+	tokcount++;
+	if (!*string)
+	    break;
+	*string++ = 0;
+    }
+    va_end(arglist);
+
+    return tokcount;
+}
+
+static char *dupstring(char *str)
+{
+    int sz = strlen(str) + 1;
+    char *new = malloc(sz);
+    if (new)
+	memcpy(new, str, sz);
+    return new;
+}
+
+static hnode_t *new_node(void *c)
+{
+    static hnode_t few[5];
+    static int count;
+
+    if (count < 5)
+	return few + count++;
+
+    return NULL;
+}
+
+static void del_node(hnode_t *n, void *c)
+{
+}
+
+int main(void)
+{
+    input_t in;
+    hash_t *h = hash_create(HASHCOUNT_T_MAX, 0, 0);
+    hnode_t *hn;
+    hscan_t hs;
+    char *tok1, *tok2, *val;
+    const char *key;
+    int prompt = 0;
+
+    char *help =
+	"a <key> <val>          add value to hash table\n"
+	"d <key>                delete value from hash table\n"
+	"l <key>                lookup value in hash table\n"
+	"n                      show size of hash table\n"
+	"c                      show number of entries\n"
+	"t                      dump whole hash table\n"
+	"+                      increase hash table (private func)\n"
+	"-                      decrease hash table (private func)\n"
+	"b                      print hash_t_bit value\n"
+	"p                      turn prompt on\n"
+	"s                      switch to non-functioning allocator\n"
+	"q                      quit";
+
+    if (!h)
+	puts("hash_create failed");
+
+    for (;;) {
+	if (prompt)
+	    putchar('>');
+	fflush(stdout);
+
+	if (!fgets(in, sizeof(input_t), stdin))
+	    break;
+
+	switch(in[0]) {
+	    case '?':
+		puts(help);
+		break;
+	    case 'b':
+		printf("%d\n", hash_val_t_bit);
+		break;
+	    case 'a':
+		if (tokenize(in+1, &tok1, &tok2, (char **) 0) != 2) {
+		    puts("what?");
+		    break;
+		}
+		key = dupstring(tok1);
+		val = dupstring(tok2);
+
+		if (!key || !val) {
+		    puts("out of memory");
+		    free((void *) key);
+		    free(val);
+		}
+
+		if (!hash_alloc_insert(h, key, val)) {
+		    puts("hash_alloc_insert failed");
+		    free((void *) key);
+		    free(val);
+		    break;
+		}
+		break;
+	    case 'd':
+		if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+		    puts("what?");
+		    break;
+		}
+		hn = hash_lookup(h, tok1);
+		if (!hn) {
+		    puts("hash_lookup failed");
+		    break;
+		}
+		val = hnode_get(hn);
+		key = hnode_getkey(hn);
+		hash_scan_delfree(h, hn);
+		free((void *) key);
+		free(val);
+		break;
+	    case 'l':
+		if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+		    puts("what?");
+		    break;
+		}
+		hn = hash_lookup(h, tok1);
+		if (!hn) {
+		    puts("hash_lookup failed");
+		    break;
+		}
+		val = hnode_get(hn);
+		puts(val);
+		break;
+	    case 'n':
+		printf("%lu\n", (unsigned long) hash_size(h));
+		break;
+	    case 'c':
+		printf("%lu\n", (unsigned long) hash_count(h));
+		break;
+	    case 't':
+		hash_scan_begin(&hs, h);
+		while ((hn = hash_scan_next(&hs)))
+		    printf("%s\t%s\n", (char*) hnode_getkey(hn),
+			    (char*) hnode_get(hn));
+		break;
+	    case '+':
+		grow_table(h);		/* private function	*/
+		break;
+	    case '-':
+		shrink_table(h);	/* private function	*/
+		break;
+	    case 'q':
+		exit(0);
+		break;
+	    case '\0':
+		break;
+	    case 'p':
+		prompt = 1;
+		break;
+	    case 's':
+		hash_set_allocator(h, new_node, del_node, NULL);
+		break;
+	    default:
+		putchar('?');
+		putchar('\n');
+		break;
+	}
+    }
+
+    return 0;
+}
diff --git a/libutil/kazlib/drivers/list-main.c b/libutil/kazlib/drivers/list-main.c
new file mode 100644
index 0000000..6f462e4
--- /dev/null
+++ b/libutil/kazlib/drivers/list-main.c
@@ -0,0 +1,152 @@
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+typedef char input_t[256];
+
+static int tokenize(char *string, ...)
+{
+    char **tokptr; 
+    va_list arglist;
+    int tokcount = 0;
+
+    va_start(arglist, string);
+    tokptr = va_arg(arglist, char **);
+    while (tokptr) {
+	while (*string && isspace((unsigned char) *string))
+	    string++;
+	if (!*string)
+	    break;
+	*tokptr = string;
+	while (*string && !isspace((unsigned char) *string))
+	    string++;
+	tokptr = va_arg(arglist, char **);
+	tokcount++;
+	if (!*string)
+	    break;
+	*string++ = 0;
+    }
+    va_end(arglist);
+
+    return tokcount;
+}
+
+static int comparef(const void *key1, const void *key2)
+{
+    return strcmp(key1, key2);
+}
+
+static char *dupstring(char *str)
+{
+    int sz = strlen(str) + 1;
+    char *new = malloc(sz);
+    if (new)
+	memcpy(new, str, sz);
+    return new;
+}
+
+int main(void)
+{
+    input_t in;
+    list_t *l = list_create(LISTCOUNT_T_MAX);
+    lnode_t *ln;
+    char *tok1, *val;
+    int prompt = 0;
+
+    char *help =
+	"a <val>                append value to list\n"
+	"d <val>                delete value from list\n"
+	"l <val>                lookup value in list\n"
+	"s                      sort list\n"
+	"c                      show number of entries\n"
+	"t                      dump whole list\n"
+	"p                      turn prompt on\n"
+	"q                      quit";
+
+    if (!l)
+	puts("list_create failed");
+
+    for (;;) {
+	if (prompt)
+	    putchar('>');
+	fflush(stdout);
+
+	if (!fgets(in, sizeof(input_t), stdin))
+	    break;
+
+	switch(in[0]) {
+	    case '?':
+		puts(help);
+		break;
+	    case 'a':
+		if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+		    puts("what?");
+		    break;
+		}
+		val = dupstring(tok1);
+		ln = lnode_create(val);
+	
+		if (!val || !ln) {
+		    puts("allocation failure");
+		    if (ln)
+			lnode_destroy(ln);
+		    free(val);
+		    break;
+		}
+    
+		list_append(l, ln);
+		break;
+	    case 'd':
+		if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+		    puts("what?");
+		    break;
+		}
+		ln = list_find(l, tok1, comparef);
+		if (!ln) {
+		    puts("list_find failed");
+		    break;
+		}
+		list_delete(l, ln);
+		val = lnode_get(ln);
+		lnode_destroy(ln);
+		free(val);
+		break;
+	    case 'l':
+		if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+		    puts("what?");
+		    break;
+		}
+		ln = list_find(l, tok1, comparef);
+		if (!ln)
+		    puts("list_find failed");
+		else
+		    puts("found");
+		break;
+	    case 's':
+		list_sort(l, comparef);
+		break;
+	    case 'c':
+		printf("%lu\n", (unsigned long) list_count(l));
+		break;
+	    case 't':
+		for (ln = list_first(l); ln != 0; ln = list_next(l, ln))
+		    puts(lnode_get(ln));
+		break;
+	    case 'q':
+		exit(0);
+		break;
+	    case '\0':
+		break;
+	    case 'p':
+		prompt = 1;
+		break;
+	    default:
+		putchar('?');
+		putchar('\n');
+		break;
+	}
+    }
+
+    return 0;
+}
diff --git a/libutil/kazlib/drivers/sfx-main.c b/libutil/kazlib/drivers/sfx-main.c
new file mode 100644
index 0000000..fda683b
--- /dev/null
+++ b/libutil/kazlib/drivers/sfx-main.c
@@ -0,0 +1,41 @@
+#include <stdlib.h>
+
+int main(int argc, char **argv)
+{
+    char expr_buf[256];
+    char *expr, *ptr;
+    sfx_rating_t eff;
+
+    for (;;) {
+	if (argc < 2) {
+	    expr = expr_buf;
+	    if (fgets(expr_buf, sizeof expr_buf, stdin) == 0)
+		break;
+	    if ((ptr = strchr(expr_buf, '\n')) != 0)
+		*ptr = 0;
+	} else {
+	    expr = (argv++)[1];
+	    if (!expr)
+		break;
+	}
+
+	if (!sfx_determine(expr, &eff)) {
+	    printf("expression '%s' has a syntax error\n", expr);
+	    return EXIT_FAILURE;
+	}
+
+	switch (eff) {
+	case sfx_none:
+	    printf("expression '%s' has no side effects\n", expr);
+	    break;
+	case sfx_potential:
+	    printf("expression '%s' may have side effects\n", expr);
+	    break;
+	case sfx_certain:
+	    printf("expression '%s' has side effects\n", expr);
+	    break;
+	}
+    }
+
+    return 0;
+}
diff --git a/libutil/kazlib/except.c b/libutil/kazlib/except.c
new file mode 100644
index 0000000..c915dda
--- /dev/null
+++ b/libutil/kazlib/except.c
@@ -0,0 +1,347 @@
+/*
+ * Portable Exception Handling for ANSI C.
+ * Copyright (C) 1999 Kaz Kylheku <kaz at ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <limits.h>
+#include "except.h"
+
+#define XCEPT_BUFFER_SIZE	1024
+
+#define group except_group
+#define code except_code
+#define id except_id
+#define message except_message
+#define dyndata except_dyndata
+#define func except_func
+#define context except_context
+#define id except_id
+#define size except_size
+#define obj except_obj
+#define jmp except_jmp
+#define down except_down
+#define type except_type
+#define catcher except_catcher
+#define cleanup except_cleanup
+#define info except_info
+
+#ifdef KAZLIB_POSIX_THREADS
+
+#include <pthread.h>
+
+static pthread_mutex_t init_mtx = PTHREAD_MUTEX_INITIALIZER;
+static int init_counter;
+static pthread_key_t top_key;
+static pthread_key_t uh_key;
+static pthread_key_t alloc_key;
+static pthread_key_t dealloc_key;
+static void unhandled_catcher(except_t *);
+
+#define get_top() ((struct except_stacknode *) pthread_getspecific(top_key))
+#define set_top(T) (pthread_setspecific(top_key, (T)), (void)((T) == (struct except_stacknode *) 0))
+#define set_catcher(C) (pthread_setspecific(uh_key, (void *) (C)), (void)((C) == (void (*)(except_t *)) 0))
+#define set_alloc(A) (pthread_setspecific(alloc_key, (void *) (A)), (void)((A) == (void *(*)(size_t)) 0))
+#define set_dealloc(D) (pthread_setspecific(dealloc_key, (void *) (D)), (void)((D) == (void (*)(void *)) 0))
+
+static void (*get_catcher(void))(except_t *)
+{
+    void (*catcher)(except_t *) = (void (*)(except_t *)) pthread_getspecific(uh_key);
+    return (catcher == 0) ? unhandled_catcher : catcher;
+}
+
+static void *(*get_alloc(void))(size_t)
+{
+    void *(*alloc)(size_t) = (void *(*)(size_t)) pthread_getspecific(alloc_key);
+    return (alloc == 0) ? malloc : alloc;
+}
+
+static void (*get_dealloc(void))(void *)
+{
+    void (*dealloc)(void *) = (void (*)(void *)) pthread_getspecific(dealloc_key);
+    return (dealloc == 0) ? free : dealloc;
+}
+
+int except_init(void)
+{
+    int retval = 1;
+
+    pthread_mutex_lock(&init_mtx);
+
+    assert (init_counter < INT_MAX);
+
+    if (init_counter++ == 0) {
+	int top_ok = (pthread_key_create(&top_key, 0) == 0);
+	int uh_ok = (pthread_key_create(&uh_key, 0) == 0);
+	int alloc_ok = (pthread_key_create(&alloc_key, 0) == 0);
+	int dealloc_ok = (pthread_key_create(&dealloc_key, 0) == 0);
+       
+	if (!top_ok || !uh_ok || !alloc_ok || !dealloc_ok) {
+	    retval = 0;
+	    init_counter = 0;
+	    if (top_ok)
+		pthread_key_delete(top_key);
+	    if (uh_ok)
+		pthread_key_delete(uh_key);
+	    if (alloc_ok)
+		pthread_key_delete(alloc_key);
+	    if (dealloc_ok)
+		pthread_key_delete(dealloc_key);
+	}
+    }
+
+    pthread_mutex_unlock(&init_mtx);
+
+    return retval;
+}
+
+void except_deinit(void)
+{
+    pthread_mutex_lock(&init_mtx);
+
+    assert (init_counter > 0);
+
+    if (--init_counter == 0) {
+	pthread_key_delete(top_key);
+	pthread_key_delete(uh_key);
+	pthread_key_delete(alloc_key);
+	pthread_key_delete(dealloc_key);
+    }
+
+    pthread_mutex_unlock(&init_mtx);
+}
+
+#else	/* no thread support */
+
+static int init_counter;
+static void unhandled_catcher(except_t *);
+static void (*uh_catcher_ptr)(except_t *) = unhandled_catcher;
+static void *(*allocator)(size_t) = malloc;
+static void (*deallocator)(void *) = free;
+static struct except_stacknode *stack_top;
+
+#define get_top() (stack_top)
+#define set_top(T) (stack_top = (T))
+#define get_catcher() (uh_catcher_ptr)
+#define set_catcher(C) (uh_catcher_ptr = (C))
+#define get_alloc() (allocator)
+#define set_alloc(A) (allocator = (A))
+#define get_dealloc() (deallocator)
+#define set_dealloc(D) (deallocator = (D))
+
+int except_init(void)
+{
+    assert (init_counter < INT_MAX);
+    init_counter++;
+    return 1;
+}
+
+void except_deinit(void)
+{
+    assert (init_counter > 0);
+    init_counter--;
+}
+
+#endif
+
+
+static int match(const volatile except_id_t *thrown, const except_id_t *caught)
+{
+    int group_match = (caught->group == XCEPT_GROUP_ANY || caught->group == thrown->group);
+    int code_match = (caught->code == XCEPT_CODE_ANY || caught->code == thrown->code);
+
+    return group_match && code_match;
+}
+
+static void do_throw(except_t *except)
+{
+    struct except_stacknode *top;
+
+    assert (except->id.group != 0 && except->id.code != 0);
+
+    for (top = get_top(); top != 0; top = top->down) {
+	if (top->type == XCEPT_CLEANUP) {
+	    top->info.cleanup->func(top->info.cleanup->context);
+	} else {
+	    struct except_catch *catcher = top->info.catcher;
+	    const except_id_t *pi = catcher->id;
+	    size_t i;
+	
+	    assert (top->type == XCEPT_CATCHER);
+	    except_free(catcher->obj.dyndata);
+
+	    for (i = 0; i < catcher->size; pi++, i++) {
+		if (match(&except->id, pi)) {
+		    catcher->obj = *except;
+		    set_top(top);
+		    longjmp(catcher->jmp, 1);
+		}
+	    }
+	}
+    }
+
+    set_top(top);
+    get_catcher()(except);	/* unhandled exception */
+    abort();
+}
+
+static void unhandled_catcher(except_t *except)
+{
+    fprintf(stderr, "Unhandled exception (\"%s\", group=%ld, code=%ld)\n",
+	    except->message, except->id.group, except->id.code);
+    abort();
+}
+
+static void stack_push(struct except_stacknode *node)
+{
+    node->down = get_top();
+    set_top(node);
+}
+
+void except_setup_clean(struct except_stacknode *esn,
+	struct except_cleanup *ecl, void (*cleanf)(void *), void *context)
+{
+    esn->type = XCEPT_CLEANUP;
+    ecl->func = cleanf;
+    ecl->context = context;
+    esn->info.cleanup = ecl;
+    stack_push(esn);
+}
+
+void except_setup_try(struct except_stacknode *esn,
+	struct except_catch *ech, const except_id_t id[], size_t size)
+{
+   ech->id = id;
+   ech->size = size;
+   ech->obj.dyndata = 0;
+   esn->type = XCEPT_CATCHER;
+   esn->info.catcher = ech;
+   stack_push(esn);
+}
+
+struct except_stacknode *except_pop(void)
+{
+    struct except_stacknode *top = get_top();
+    set_top(top->down);
+    return top;
+}
+
+void except_rethrow(except_t *except)
+{
+    struct except_stacknode *top = get_top();
+    assert (top != 0);
+    assert (top->type == XCEPT_CATCHER);
+    assert (&top->info.catcher->obj == except);
+    set_top(top->down);
+    do_throw(except);
+}
+
+void except_throw(long group, long code, const char *msg)
+{
+    except_t except;
+
+    except.id.group = group;
+    except.id.code = code;
+    except.message = msg;
+    except.dyndata = 0;
+
+    do_throw(&except);
+}
+
+void except_throwd(long group, long code, const char *msg, void *data)
+{
+    except_t except;
+
+    except.id.group = group;
+    except.id.code = code;
+    except.message = msg;
+    except.dyndata = data;
+
+    do_throw(&except);
+}
+
+void except_throwf(long group, long code, const char *fmt, ...)
+{
+    char *buf = except_alloc(XCEPT_BUFFER_SIZE);
+    va_list vl;
+
+    va_start (vl, fmt);
+    vsprintf(buf, fmt, vl);
+    va_end (vl);
+    except_throwd(group, code, buf, buf);
+}
+
+void (*except_unhandled_catcher(void (*new_catcher)(except_t *)))(except_t *)
+{
+    void (*old_catcher)(except_t *) = get_catcher();
+    set_catcher(new_catcher);
+    return old_catcher;
+}
+
+#undef except_code
+#undef except_group
+#undef except_message
+#undef except_data
+
+unsigned long except_code(except_t *ex)
+{
+    return ex->id.code;
+}
+
+unsigned long except_group(except_t *ex)
+{
+    return ex->id.group;
+}
+
+const char *except_message(except_t *ex)
+{
+    return ex->message;
+}
+
+void *except_data(except_t *ex)
+{
+    return ex->dyndata;
+}
+
+void *except_take_data(except_t *ex)
+{
+    void *data = ex->dyndata;
+    ex->dyndata = 0;
+    return data;
+}
+
+void except_set_allocator(void *(*alloc)(size_t), void (*dealloc)(void *))
+{
+    set_alloc(alloc);
+    set_dealloc(dealloc);
+}
+
+void *except_alloc(size_t size)
+{
+    void *ptr = get_alloc()(size);
+
+    if (ptr == 0)
+	except_throw(XCEPT_BAD_ALLOC, 0, "out of memory");
+    return ptr;
+}
+
+void except_free(void *ptr)
+{
+    get_dealloc()(ptr);
+}
diff --git a/libutil/kazlib/except.h b/libutil/kazlib/except.h
new file mode 100644
index 0000000..3131fb9
--- /dev/null
+++ b/libutil/kazlib/except.h
@@ -0,0 +1,147 @@
+/*
+ * Portable Exception Handling for ANSI C.
+ * Copyright (C) 1999 Kaz Kylheku <kaz at ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ */
+
+#ifndef XCEPT_H
+#define XCEPT_H
+
+#include <setjmp.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#define XCEPT_GROUP_ANY	0
+#define XCEPT_CODE_ANY	0
+#define XCEPT_BAD_ALLOC 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum { except_no_call, except_call };
+
+typedef struct {
+    unsigned long except_group;
+    unsigned long except_code;
+} except_id_t;
+
+typedef struct {
+    except_id_t volatile except_id;
+    const char *volatile except_message;
+    void *volatile except_dyndata;
+} except_t;
+
+struct except_cleanup {
+    void (*except_func)(void *);
+    void *except_context;
+};
+
+struct except_catch {
+    const except_id_t *except_id;
+    size_t except_size;
+    except_t except_obj;
+    jmp_buf except_jmp;
+};
+
+enum except_stacktype {
+    XCEPT_CLEANUP, XCEPT_CATCHER
+};
+
+struct except_stacknode {
+    struct except_stacknode *except_down;
+    enum except_stacktype except_type;
+    union {
+	struct except_catch *except_catcher;	
+	struct except_cleanup *except_cleanup;
+    } except_info;
+};
+
+/* private functions made external so they can be used in macros */
+void except_setup_clean(struct except_stacknode *,
+	struct except_cleanup *, void (*)(void *), void *);
+void except_setup_try(struct except_stacknode *,
+	struct except_catch *, const except_id_t [], size_t);
+struct except_stacknode *except_pop(void);
+
+/* public interface functions */
+int except_init(void);
+void except_deinit(void);
+void except_rethrow(except_t *);
+void except_throw(long, long, const char *);
+void except_throwd(long, long, const char *, void *);
+void except_throwf(long, long, const char *, ...);
+void (*except_unhandled_catcher(void (*)(except_t *)))(except_t *);
+unsigned long except_code(except_t *);
+unsigned long except_group(except_t *);
+const char *except_message(except_t *);
+void *except_data(except_t *);
+void *except_take_data(except_t *);
+void except_set_allocator(void *(*)(size_t), void (*)(void *));
+void *except_alloc(size_t);
+void except_free(void *);
+
+#define except_code(E) ((E)->except_id.except_code)
+#define except_group(E) ((E)->except_id.except_group)
+#define except_message(E) ((E)->except_message)
+#define except_data(E) ((E)->except_dyndata)
+
+#ifdef __cplusplus
+}
+#endif
+
+/*
+ * void except_cleanup_push(void (*)(void *), void *); 
+ * void except_cleanup_pop(int);
+ * void except_checked_cleanup_pop(void (*)(void *), int);
+ * void except_try_push(const except_id_t [], size_t, except_t **);
+ * void except_try_pop(void);
+ */
+
+#define except_cleanup_push(F, C) 				\
+    {								\
+	struct except_stacknode except_sn;			\
+	struct except_cleanup except_cl;			\
+	except_setup_clean(&except_sn, &except_cl, F, C)
+
+#define except_cleanup_pop(E)					\
+	except_pop();						\
+	if (E)							\
+	    except_cl.except_func(except_cl.except_context);	\
+    }
+
+#define except_checked_cleanup_pop(F, E)			\
+    	except_pop();						\
+	assert (except_cl.except_func == (F));			\
+	if (E)							\
+	    except_cl.except_func(except_cl.except_context);	\
+    }
+	
+#define except_try_push(ID, NUM, PPE)				\
+     {								\
+	struct except_stacknode except_sn;			\
+	struct except_catch except_ch;				\
+	except_setup_try(&except_sn, &except_ch, ID, NUM);	\
+	if (setjmp(except_ch.except_jmp))			\
+	    *(PPE) = &except_ch.except_obj;			\
+	else							\
+	    *(PPE) = 0
+
+#define except_try_pop()					\
+	except_free(except_ch.except_obj.except_dyndata);	\
+	except_pop();						\
+    } 
+
+#endif
diff --git a/libutil/kazlib/hash.c b/libutil/kazlib/hash.c
new file mode 100644
index 0000000..2140e66
--- /dev/null
+++ b/libutil/kazlib/hash.c
@@ -0,0 +1,837 @@
+/*
+ * Hash Table Data Type
+ * Copyright (C) 1997 Kaz Kylheku <kaz at ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ */
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <assert.h>
+#include <string.h>
+#define HASH_IMPLEMENTATION
+#include "hash.h"
+
+#define INIT_BITS	6
+#define INIT_SIZE	(1UL << (INIT_BITS))	/* must be power of two		*/
+#define INIT_MASK	((INIT_SIZE) - 1)
+
+#define next hash_next
+#define key hash_key
+#define data hash_data
+#define hkey hash_hkey
+
+#define table hash_table
+#define nchains hash_nchains
+#define nodecount hash_nodecount
+#define maxcount hash_maxcount
+#define highmark hash_highmark
+#define lowmark hash_lowmark
+#define compare hash_compare
+#define function hash_function
+#define allocnode hash_allocnode
+#define freenode hash_freenode
+#define context hash_context
+#define mask hash_mask
+#define dynamic hash_dynamic
+
+#define table hash_table
+#define chain hash_chain
+
+static hnode_t *hnode_alloc(void *context);
+static void hnode_free(hnode_t *node, void *context);
+static hash_val_t hash_fun_default(const void *key);
+static int hash_comp_default(const void *key1, const void *key2);
+
+int hash_val_t_bit;
+
+/*
+ * Compute the number of bits in the hash_val_t type.  We know that hash_val_t
+ * is an unsigned integral type. Thus the highest value it can hold is a
+ * Mersenne number (power of two, less one). We initialize a hash_val_t
+ * object with this value and then shift bits out one by one while counting.
+ * Notes:
+ * 1. HASH_VAL_T_MAX is a Mersenne number---one that is one less than a power
+ *    of two. This means that its binary representation consists of all one
+ *    bits, and hence ``val'' is initialized to all one bits.
+ * 2. While bits remain in val, we increment the bit count and shift it to the
+ *    right, replacing the topmost bit by zero.
+ */
+
+static void compute_bits(void)
+{
+    hash_val_t val = HASH_VAL_T_MAX;	/* 1 */
+    int bits = 0;
+
+    while (val) {	/* 2 */
+	bits++;
+	val >>= 1;
+    }
+
+    hash_val_t_bit = bits;
+}
+
+/*
+ * Verify whether the given argument is a power of two.
+ */
+
+static int is_power_of_two(hash_val_t arg)
+{
+    if (arg == 0)
+	return 0;
+    while ((arg & 1) == 0)
+	arg >>= 1;
+    return (arg == 1);
+}
+
+/*
+ * Compute a shift amount from a given table size 
+ */
+
+static hash_val_t compute_mask(hashcount_t size)
+{
+    assert (is_power_of_two(size));
+    assert (size >= 2);
+
+    return size - 1;
+}
+
+/*
+ * Initialize the table of pointers to null.
+ */
+
+static void clear_table(hash_t *hash)
+{
+    hash_val_t i;
+
+    for (i = 0; i < hash->nchains; i++)
+	hash->table[i] = NULL;
+}
+
+/*
+ * Double the size of a dynamic table. This works as follows. Each chain splits
+ * into two adjacent chains.  The shift amount increases by one, exposing an
+ * additional bit of each hashed key. For each node in the original chain, the
+ * value of this newly exposed bit will decide which of the two new chains will
+ * receive the node: if the bit is 1, the chain with the higher index will have
+ * the node, otherwise the lower chain will receive the node. In this manner,
+ * the hash table will continue to function exactly as before without having to
+ * rehash any of the keys.
+ * Notes:
+ * 1.  Overflow check.
+ * 2.  The new number of chains is twice the old number of chains.
+ * 3.  The new mask is one bit wider than the previous, revealing a
+ *     new bit in all hashed keys.
+ * 4.  Allocate a new table of chain pointers that is twice as large as the
+ *     previous one.
+ * 5.  If the reallocation was successful, we perform the rest of the growth
+ *     algorithm, otherwise we do nothing.
+ * 6.  The exposed_bit variable holds a mask with which each hashed key can be
+ *     AND-ed to test the value of its newly exposed bit.
+ * 7.  Now loop over each chain in the table and sort its nodes into two
+ *     chains based on the value of each node's newly exposed hash bit.
+ * 8.  The low chain replaces the current chain.  The high chain goes
+ *     into the corresponding sister chain in the upper half of the table.
+ * 9.  We have finished dealing with the chains and nodes. We now update
+ *     the various bookeeping fields of the hash structure.
+ */
+
+static void grow_table(hash_t *hash)
+{
+    hnode_t **newtable;
+
+    assert (2 * hash->nchains > hash->nchains);	/* 1 */
+
+    newtable = realloc(hash->table,
+	    sizeof *newtable * hash->nchains * 2);	/* 4 */
+
+    if (newtable) {	/* 5 */
+	hash_val_t mask = (hash->mask << 1) | 1;	/* 3 */
+	hash_val_t exposed_bit = mask ^ hash->mask;	/* 6 */
+	hash_val_t chain;
+
+	assert (mask != hash->mask);
+
+	for (chain = 0; chain < hash->nchains; chain++) { /* 7 */
+	    hnode_t *low_chain = 0, *high_chain = 0, *hptr, *next;
+
+	    for (hptr = newtable[chain]; hptr != 0; hptr = next) {
+		next = hptr->next;
+
+		if (hptr->hkey & exposed_bit) {
+		    hptr->next = high_chain;
+		    high_chain = hptr;
+		} else {
+		    hptr->next = low_chain;
+		    low_chain = hptr;
+		}
+	    }
+
+	    newtable[chain] = low_chain; 	/* 8 */
+	    newtable[chain + hash->nchains] = high_chain;
+	}
+
+	hash->table = newtable;			/* 9 */
+	hash->mask = mask;
+	hash->nchains *= 2;
+	hash->lowmark *= 2;
+	hash->highmark *= 2;
+    }
+    assert (hash_verify(hash));
+}
+
+/*
+ * Cut a table size in half. This is done by folding together adjacent chains
+ * and populating the lower half of the table with these chains. The chains are
+ * simply spliced together. Once this is done, the whole table is reallocated
+ * to a smaller object.
+ * Notes:
+ * 1.  It is illegal to have a hash table with one slot. This would mean that
+ *     hash->shift is equal to hash_val_t_bit, an illegal shift value.
+ *     Also, other things could go wrong, such as hash->lowmark becoming zero.
+ * 2.  Looping over each pair of sister chains, the low_chain is set to
+ *     point to the head node of the chain in the lower half of the table, 
+ *     and high_chain points to the head node of the sister in the upper half.
+ * 3.  The intent here is to compute a pointer to the last node of the
+ *     lower chain into the low_tail variable. If this chain is empty,
+ *     low_tail ends up with a null value.
+ * 4.  If the lower chain is not empty, we simply tack the upper chain onto it.
+ *     If the upper chain is a null pointer, nothing happens.
+ * 5.  Otherwise if the lower chain is empty but the upper one is not,
+ *     If the low chain is empty, but the high chain is not, then the
+ *     high chain is simply transferred to the lower half of the table.
+ * 6.  Otherwise if both chains are empty, there is nothing to do.
+ * 7.  All the chain pointers are in the lower half of the table now, so
+ *     we reallocate it to a smaller object. This, of course, invalidates
+ *     all pointer-to-pointers which reference into the table from the
+ *     first node of each chain.
+ * 8.  Though it's unlikely, the reallocation may fail. In this case we
+ *     pretend that the table _was_ reallocated to a smaller object.
+ * 9.  Finally, update the various table parameters to reflect the new size.
+ */
+
+static void shrink_table(hash_t *hash)
+{
+    hash_val_t chain, nchains;
+    hnode_t **newtable, *low_tail, *low_chain, *high_chain;
+
+    assert (hash->nchains >= 2);			/* 1 */
+    nchains = hash->nchains / 2;
+
+    for (chain = 0; chain < nchains; chain++) {
+	low_chain = hash->table[chain];		/* 2 */
+	high_chain = hash->table[chain + nchains];
+	for (low_tail = low_chain; low_tail && low_tail->next; low_tail = low_tail->next)
+	    ;	/* 3 */
+	if (low_chain != 0)				/* 4 */
+	    low_tail->next = high_chain;
+	else if (high_chain != 0)			/* 5 */
+	    hash->table[chain] = high_chain;
+	else
+	    assert (hash->table[chain] == NULL);	/* 6 */
+    }
+    newtable = realloc(hash->table,
+	    sizeof *newtable * nchains);		/* 7 */
+    if (newtable)					/* 8 */
+	hash->table = newtable;
+    hash->mask >>= 1;			/* 9 */
+    hash->nchains = nchains;
+    hash->lowmark /= 2;
+    hash->highmark /= 2;
+    assert (hash_verify(hash));
+}
+
+
+/*
+ * Create a dynamic hash table. Both the hash table structure and the table
+ * itself are dynamically allocated. Furthermore, the table is extendible in
+ * that it will automatically grow as its load factor increases beyond a
+ * certain threshold.
+ * Notes:
+ * 1. If the number of bits in the hash_val_t type has not been computed yet,
+ *    we do so here, because this is likely to be the first function that the
+ *    user calls.
+ * 2. Allocate a hash table control structure.
+ * 3. If a hash table control structure is successfully allocated, we
+ *    proceed to initialize it. Otherwise we return a null pointer.
+ * 4. We try to allocate the table of hash chains.
+ * 5. If we were able to allocate the hash chain table, we can finish
+ *    initializing the hash structure and the table. Otherwise, we must
+ *    backtrack by freeing the hash structure.
+ * 6. INIT_SIZE should be a power of two. The high and low marks are always set
+ *    to be twice the table size and half the table size respectively. When the
+ *    number of nodes in the table grows beyond the high size (beyond load
+ *    factor 2), it will double in size to cut the load factor down to about
+ *    about 1. If the table shrinks down to or beneath load factor 0.5,
+ *    it will shrink, bringing the load up to about 1. However, the table
+ *    will never shrink beneath INIT_SIZE even if it's emptied.
+ * 7. This indicates that the table is dynamically allocated and dynamically
+ *    resized on the fly. A table that has this value set to zero is
+ *    assumed to be statically allocated and will not be resized.
+ * 8. The table of chains must be properly reset to all null pointers.
+ */
+
+hash_t *hash_create(hashcount_t maxcount, hash_comp_t compfun,
+	hash_fun_t hashfun)
+{
+    hash_t *hash;
+
+    if (hash_val_t_bit == 0)	/* 1 */
+	compute_bits();
+
+    hash = malloc(sizeof *hash);	/* 2 */
+
+    if (hash) {		/* 3 */
+	hash->table = malloc(sizeof *hash->table * INIT_SIZE);	/* 4 */
+	if (hash->table) {	/* 5 */
+	    hash->nchains = INIT_SIZE;		/* 6 */
+	    hash->highmark = INIT_SIZE * 2;
+	    hash->lowmark = INIT_SIZE / 2;
+	    hash->nodecount = 0;
+	    hash->maxcount = maxcount;
+	    hash->compare = compfun ? compfun : hash_comp_default;
+	    hash->function = hashfun ? hashfun : hash_fun_default;
+	    hash->allocnode = hnode_alloc;
+	    hash->freenode = hnode_free;
+	    hash->context = NULL;
+	    hash->mask = INIT_MASK;
+	    hash->dynamic = 1;			/* 7 */
+	    clear_table(hash);			/* 8 */
+	    assert (hash_verify(hash));
+	    return hash;
+	} 
+	free(hash);
+    }
+
+    return NULL;
+}
+
+/*
+ * Select a different set of node allocator routines.
+ */
+
+void hash_set_allocator(hash_t *hash, hnode_alloc_t al,
+	hnode_free_t fr, void *context)
+{
+    assert (hash_count(hash) == 0);
+    assert ((al == 0 && fr == 0) || (al != 0 && fr != 0));
+
+    hash->allocnode = al ? al : hnode_alloc;
+    hash->freenode = fr ? fr : hnode_free;
+    hash->context = context;
+}
+
+/*
+ * Free every node in the hash using the hash->freenode() function pointer, and
+ * cause the hash to become empty.
+ */
+
+void hash_free_nodes(hash_t *hash)
+{
+    hscan_t hs;
+    hnode_t *node;
+    hash_scan_begin(&hs, hash);
+    while ((node = hash_scan_next(&hs))) {
+	hash_scan_delete(hash, node);
+	hash->freenode(node, hash->context);
+    }
+    hash->nodecount = 0;
+    clear_table(hash);
+}
+
+/*
+ * Obsolescent function for removing all nodes from a table,
+ * freeing them and then freeing the table all in one step.
+ */
+
+void hash_free(hash_t *hash)
+{
+#ifdef KAZLIB_OBSOLESCENT_DEBUG
+    assert ("call to obsolescent function hash_free()" && 0);
+#endif
+    hash_free_nodes(hash);
+    hash_destroy(hash);
+}
+
+/*
+ * Free a dynamic hash table structure.
+ */
+
+void hash_destroy(hash_t *hash)
+{
+    assert (hash_val_t_bit != 0);
+    assert (hash_isempty(hash));
+    free(hash->table);
+    free(hash);
+}
+
+/*
+ * Initialize a user supplied hash structure. The user also supplies a table of
+ * chains which is assigned to the hash structure. The table is static---it
+ * will not grow or shrink.
+ * 1. See note 1. in hash_create().
+ * 2. The user supplied array of pointers hopefully contains nchains nodes.
+ * 3. See note 7. in hash_create().
+ * 4. We must dynamically compute the mask from the given power of two table
+ *    size. 
+ * 5. The user supplied table can't be assumed to contain null pointers,
+ *    so we reset it here.
+ */
+
+hash_t *hash_init(hash_t *hash, hashcount_t maxcount,
+	hash_comp_t compfun, hash_fun_t hashfun, hnode_t **table,
+	hashcount_t nchains)
+{
+    if (hash_val_t_bit == 0)	/* 1 */
+	compute_bits();
+
+    assert (is_power_of_two(nchains));
+
+    hash->table = table;	/* 2 */
+    hash->nchains = nchains;
+    hash->nodecount = 0;
+    hash->maxcount = maxcount;
+    hash->compare = compfun ? compfun : hash_comp_default;
+    hash->function = hashfun ? hashfun : hash_fun_default;
+    hash->dynamic = 0;		/* 3 */
+    hash->mask = compute_mask(nchains);	/* 4 */
+    clear_table(hash);		/* 5 */
+
+    assert (hash_verify(hash));
+
+    return hash;
+}
+
+/*
+ * Reset the hash scanner so that the next element retrieved by
+ * hash_scan_next() shall be the first element on the first non-empty chain. 
+ * Notes:
+ * 1. Locate the first non empty chain.
+ * 2. If an empty chain is found, remember which one it is and set the next
+ *    pointer to refer to its first element.
+ * 3. Otherwise if a chain is not found, set the next pointer to NULL
+ *    so that hash_scan_next() shall indicate failure.
+ */
+
+void hash_scan_begin(hscan_t *scan, hash_t *hash)
+{
+    hash_val_t nchains = hash->nchains;
+    hash_val_t chain;
+
+    scan->table = hash;
+
+    /* 1 */
+
+    for (chain = 0; chain < nchains && hash->table[chain] == 0; chain++)
+	;
+
+    if (chain < nchains) {	/* 2 */
+	scan->chain = chain;
+	scan->next = hash->table[chain];
+    } else {			/* 3 */
+	scan->next = NULL;
+    }
+}
+
+/*
+ * Retrieve the next node from the hash table, and update the pointer
+ * for the next invocation of hash_scan_next(). 
+ * Notes:
+ * 1. Remember the next pointer in a temporary value so that it can be
+ *    returned.
+ * 2. This assertion essentially checks whether the module has been properly
+ *    initialized. The first point of interaction with the module should be
+ *    either hash_create() or hash_init(), both of which set hash_val_t_bit to
+ *    a non zero value.
+ * 3. If the next pointer we are returning is not NULL, then the user is
+ *    allowed to call hash_scan_next() again. We prepare the new next pointer
+ *    for that call right now. That way the user is allowed to delete the node
+ *    we are about to return, since we will no longer be needing it to locate
+ *    the next node.
+ * 4. If there is a next node in the chain (next->next), then that becomes the
+ *    new next node, otherwise ...
+ * 5. We have exhausted the current chain, and must locate the next subsequent
+ *    non-empty chain in the table.
+ * 6. If a non-empty chain is found, the first element of that chain becomes
+ *    the new next node. Otherwise there is no new next node and we set the
+ *    pointer to NULL so that the next time hash_scan_next() is called, a null
+ *    pointer shall be immediately returned.
+ */
+
+
+hnode_t *hash_scan_next(hscan_t *scan)
+{
+    hnode_t *next = scan->next;		/* 1 */
+    hash_t *hash = scan->table;
+    hash_val_t chain = scan->chain + 1;
+    hash_val_t nchains = hash->nchains;
+
+    assert (hash_val_t_bit != 0);	/* 2 */
+
+    if (next) {			/* 3 */
+	if (next->next) {	/* 4 */
+	    scan->next = next->next;
+	} else {
+	    while (chain < nchains && hash->table[chain] == 0)	/* 5 */
+	    	chain++;
+	    if (chain < nchains) {	/* 6 */
+		scan->chain = chain;
+		scan->next = hash->table[chain];
+	    } else {
+		scan->next = NULL;
+	    }
+	}
+    }
+    return next;
+}
+
+/*
+ * Insert a node into the hash table.
+ * Notes:
+ * 1. It's illegal to insert more than the maximum number of nodes. The client
+ *    should verify that the hash table is not full before attempting an
+ *    insertion.
+ * 2. The same key may not be inserted into a table twice.
+ * 3. If the table is dynamic and the load factor is already at >= 2,
+ *    grow the table.
+ * 4. We take the bottom N bits of the hash value to derive the chain index,
+ *    where N is the base 2 logarithm of the size of the hash table. 
+ */
+
+void hash_insert(hash_t *hash, hnode_t *node, const void *key)
+{
+    hash_val_t hkey, chain;
+
+    assert (hash_val_t_bit != 0);
+    assert (node->next == NULL);
+    assert (hash->nodecount < hash->maxcount);	/* 1 */
+    assert (hash_lookup(hash, key) == NULL);	/* 2 */
+
+    if (hash->dynamic && hash->nodecount >= hash->highmark)	/* 3 */
+	grow_table(hash);
+
+    hkey = hash->function(key);
+    chain = hkey & hash->mask;	/* 4 */
+
+    node->key = key;
+    node->hkey = hkey;
+    node->next = hash->table[chain];
+    hash->table[chain] = node;
+    hash->nodecount++;
+
+    assert (hash_verify(hash));
+}
+
+/*
+ * Find a node in the hash table and return a pointer to it.
+ * Notes:
+ * 1. We hash the key and keep the entire hash value. As an optimization, when
+ *    we descend down the chain, we can compare hash values first and only if
+ *    hash values match do we perform a full key comparison. 
+ * 2. To locate the chain from among 2^N chains, we look at the lower N bits of
+ *    the hash value by anding them with the current mask.
+ * 3. Looping through the chain, we compare the stored hash value inside each
+ *    node against our computed hash. If they match, then we do a full
+ *    comparison between the unhashed keys. If these match, we have located the
+ *    entry.
+ */
+
+hnode_t *hash_lookup(hash_t *hash, const void *key)
+{
+    hash_val_t hkey, chain;
+    hnode_t *nptr;
+
+    hkey = hash->function(key);		/* 1 */
+    chain = hkey & hash->mask;		/* 2 */
+
+    for (nptr = hash->table[chain]; nptr; nptr = nptr->next) {	/* 3 */
+	if (nptr->hkey == hkey && hash->compare(nptr->key, key) == 0)
+	    return nptr;
+    }
+
+    return NULL;
+}
+
+/*
+ * Delete the given node from the hash table.  Since the chains
+ * are singly linked, we must locate the start of the node's chain
+ * and traverse.
+ * Notes:
+ * 1. The node must belong to this hash table, and its key must not have
+ *    been tampered with.
+ * 2. If this deletion will take the node count below the low mark, we
+ *    shrink the table now. 
+ * 3. Determine which chain the node belongs to, and fetch the pointer
+ *    to the first node in this chain.
+ * 4. If the node being deleted is the first node in the chain, then
+ *    simply update the chain head pointer.
+ * 5. Otherwise advance to the node's predecessor, and splice out
+ *    by updating the predecessor's next pointer.
+ * 6. Indicate that the node is no longer in a hash table.
+ */
+
+hnode_t *hash_delete(hash_t *hash, hnode_t *node)
+{
+    hash_val_t chain;
+    hnode_t *hptr;
+
+    assert (hash_lookup(hash, node->key) == node);	/* 1 */
+    assert (hash_val_t_bit != 0);
+
+    if (hash->dynamic && hash->nodecount <= hash->lowmark
+	    && hash->nodecount > INIT_SIZE)
+	shrink_table(hash);				/* 2 */
+
+    chain = node->hkey & hash->mask;			/* 3 */
+    hptr = hash->table[chain];
+
+    if (hptr == node) {					/* 4 */
+	hash->table[chain] = node->next;
+    } else {
+	while (hptr->next != node) {			/* 5 */
+	    assert (hptr != 0);
+	    hptr = hptr->next;
+	}
+	assert (hptr->next == node);
+	hptr->next = node->next;
+    }
+	
+    hash->nodecount--;
+    assert (hash_verify(hash));
+
+    node->next = NULL;					/* 6 */
+    return node;
+}
+
+int hash_alloc_insert(hash_t *hash, const void *key, void *data)
+{
+    hnode_t *node = hash->allocnode(hash->context);
+
+    if (node) {
+	hnode_init(node, data);
+	hash_insert(hash, node, key);
+	return 1;
+    }
+    return 0;
+}
+
+void hash_delete_free(hash_t *hash, hnode_t *node)
+{
+    hash_delete(hash, node);
+    hash->freenode(node, hash->context);
+}
+
+/*
+ *  Exactly like hash_delete, except does not trigger table shrinkage. This is to be
+ *  used from within a hash table scan operation. See notes for hash_delete.
+ */
+
+hnode_t *hash_scan_delete(hash_t *hash, hnode_t *node)
+{
+    hash_val_t chain;
+    hnode_t *hptr;
+
+    assert (hash_lookup(hash, node->key) == node);
+    assert (hash_val_t_bit != 0);
+
+    chain = node->hkey & hash->mask;
+    hptr = hash->table[chain];
+
+    if (hptr == node) {
+	hash->table[chain] = node->next;
+    } else {
+	while (hptr->next != node) 
+	    hptr = hptr->next;
+	hptr->next = node->next;
+    }
+	
+    hash->nodecount--;
+    assert (hash_verify(hash));
+    node->next = NULL;
+
+    return node;
+}
+
+/*
+ * Like hash_delete_free but based on hash_scan_delete.
+ */
+
+void hash_scan_delfree(hash_t *hash, hnode_t *node)
+{
+    hash_scan_delete(hash, node);
+    hash->freenode(node, hash->context);
+}
+
+/*
+ * Verify whether the given object is a valid hash table. This means
+ * Notes:
+ * 1. If the hash table is dynamic, verify whether the high and
+ *    low expansion/shrinkage thresholds are powers of two.
+ * 2. Count all nodes in the table, and test each hash value
+ *    to see whether it is correct for the node's chain.
+ */
+
+int hash_verify(hash_t *hash)
+{
+    hashcount_t count = 0;
+    hash_val_t chain;
+    hnode_t *hptr;
+
+    if (hash->dynamic) {	/* 1 */
+	if (hash->lowmark >= hash->highmark)
+	    return 0;
+	if (!is_power_of_two(hash->highmark))
+	    return 0;
+	if (!is_power_of_two(hash->lowmark))
+	    return 0;
+    }
+
+    for (chain = 0; chain < hash->nchains; chain++) {	/* 2 */
+	for (hptr = hash->table[chain]; hptr != 0; hptr = hptr->next) {
+	    if ((hptr->hkey & hash->mask) != chain)
+		return 0;
+	    count++;
+	}
+    }
+
+    if (count != hash->nodecount)
+	return 0;
+
+    return 1;
+}
+
+/*
+ * Test whether the hash table is full and return 1 if this is true,
+ * 0 if it is false.
+ */
+
+#undef hash_isfull
+int hash_isfull(hash_t *hash)
+{
+    return hash->nodecount == hash->maxcount;
+}
+
+/*
+ * Test whether the hash table is empty and return 1 if this is true,
+ * 0 if it is false.
+ */
+
+#undef hash_isempty
+int hash_isempty(hash_t *hash)
+{
+    return hash->nodecount == 0;
+}
+
+static hnode_t *hnode_alloc(void *context)
+{
+    return malloc(sizeof *hnode_alloc(NULL));
+}
+
+static void hnode_free(hnode_t *node, void *context)
+{
+    free(node);
+}
+
+
+/*
+ * Create a hash table node dynamically and assign it the given data.
+ */
+
+hnode_t *hnode_create(void *data)
+{
+    hnode_t *node = malloc(sizeof *node);
+    if (node) {
+	node->data = data;
+	node->next = NULL;
+    }
+    return node;
+}
+
+/*
+ * Initialize a client-supplied node 
+ */
+
+hnode_t *hnode_init(hnode_t *hnode, void *data)
+{
+    hnode->data = data;
+    hnode->next = NULL;
+    return hnode;
+}
+
+/*
+ * Destroy a dynamically allocated node.
+ */
+
+void hnode_destroy(hnode_t *hnode)
+{
+    free(hnode);
+}
+
+#undef hnode_put
+void hnode_put(hnode_t *node, void *data)
+{
+    node->data = data;
+}
+
+#undef hnode_get
+void *hnode_get(hnode_t *node)
+{
+    return node->data;
+}
+
+#undef hnode_getkey
+const void *hnode_getkey(hnode_t *node)
+{
+    return node->key;
+}
+
+#undef hash_count
+hashcount_t hash_count(hash_t *hash)
+{
+    return hash->nodecount;
+}
+
+#undef hash_size
+hashcount_t hash_size(hash_t *hash)
+{
+    return hash->nchains;
+}
+
+static hash_val_t hash_fun_default(const void *key)
+{
+    static unsigned long randbox[] = {
+	0x49848f1bU, 0xe6255dbaU, 0x36da5bdcU, 0x47bf94e9U,
+	0x8cbcce22U, 0x559fc06aU, 0xd268f536U, 0xe10af79aU,
+	0xc1af4d69U, 0x1d2917b5U, 0xec4c304dU, 0x9ee5016cU,
+	0x69232f74U, 0xfead7bb3U, 0xe9089ab6U, 0xf012f6aeU,
+    };
+
+    const unsigned char *str = key;
+    hash_val_t acc = 0;
+
+    while (*str) {
+	acc ^= randbox[(*str + acc) & 0xf];
+	acc = (acc << 1) | (acc >> 31);
+	acc &= 0xffffffffU;
+	acc ^= randbox[((*str++ >> 4) + acc) & 0xf];
+	acc = (acc << 2) | (acc >> 30);
+	acc &= 0xffffffffU;
+    }
+    return acc;
+}
+
+static int hash_comp_default(const void *key1, const void *key2)
+{
+    return strcmp(key1, key2);
+}
diff --git a/libutil/kazlib/hash.h b/libutil/kazlib/hash.h
new file mode 100644
index 0000000..e8213f7
--- /dev/null
+++ b/libutil/kazlib/hash.h
@@ -0,0 +1,238 @@
+/*
+ * Hash Table Data Type
+ * Copyright (C) 1997 Kaz Kylheku <kaz at ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ */
+
+#ifndef HASH_H
+#define HASH_H
+
+#include <limits.h>
+#ifdef KAZLIB_SIDEEFFECT_DEBUG
+#include "sfx.h"
+#endif
+
+/*
+ * Blurb for inclusion into C++ translation units
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef unsigned long hashcount_t;
+#define HASHCOUNT_T_MAX ULONG_MAX
+
+typedef unsigned long hash_val_t;
+#define HASH_VAL_T_MAX ULONG_MAX
+
+extern int hash_val_t_bit;
+
+#ifndef HASH_VAL_T_BIT
+#define HASH_VAL_T_BIT ((int) hash_val_t_bit)
+#endif
+
+/*
+ * Hash chain node structure.
+ * Notes:
+ * 1. This preprocessing directive is for debugging purposes.  The effect is
+ *    that if the preprocessor symbol KAZLIB_OPAQUE_DEBUG is defined prior to the
+ *    inclusion of this header,  then the structure shall be declared as having
+ *    the single member   int __OPAQUE__.   This way, any attempts by the
+ *    client code to violate the principles of information hiding (by accessing
+ *    the structure directly) can be diagnosed at translation time. However,
+ *    note the resulting compiled unit is not suitable for linking.
+ * 2. This is a pointer to the next node in the chain. In the last node of a
+ *    chain, this pointer is null.
+ * 3. The key is a pointer to some user supplied data that contains a unique
+ *    identifier for each hash node in a given table. The interpretation of
+ *    the data is up to the user. When creating or initializing a hash table,
+ *    the user must supply a pointer to a function for comparing two keys,
+ *    and a pointer to a function for hashing a key into a numeric value.
+ * 4. The value is a user-supplied pointer to void which may refer to
+ *    any data object. It is not interpreted in any way by the hashing
+ *    module.
+ * 5. The hashed key is stored in each node so that we don't have to rehash
+ *    each key when the table must grow or shrink.
+ */
+
+typedef struct hnode_t {
+#if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)	/* 1 */
+    struct hnode_t *hash_next;		/* 2 */
+    const void *hash_key;		/* 3 */
+    void *hash_data;			/* 4 */
+    hash_val_t hash_hkey;		/* 5 */
+#else
+    int hash_dummy;
+#endif
+} hnode_t;
+
+/*
+ * The comparison function pointer type. A comparison function takes two keys
+ * and produces a value of -1 if the left key is less than the right key, a
+ * value of 0 if the keys are equal, and a value of 1 if the left key is
+ * greater than the right key.
+ */
+
+typedef int (*hash_comp_t)(const void *, const void *);
+
+/*
+ * The hashing function performs some computation on a key and produces an
+ * integral value of type hash_val_t based on that key. For best results, the
+ * function should have a good randomness properties in *all* significant bits
+ * over the set of keys that are being inserted into a given hash table. In
+ * particular, the most significant bits of hash_val_t are most significant to
+ * the hash module. Only as the hash table expands are less significant bits
+ * examined. Thus a function that has good distribution in its upper bits but
+ * not lower is preferrable to one that has poor distribution in the upper bits
+ * but not the lower ones.
+ */
+
+typedef hash_val_t (*hash_fun_t)(const void *);
+
+/*
+ * allocator functions
+ */
+
+typedef hnode_t *(*hnode_alloc_t)(void *);
+typedef void (*hnode_free_t)(hnode_t *, void *);
+
+/*
+ * This is the hash table control structure. It keeps track of information
+ * about a hash table, as well as the hash table itself.
+ * Notes:
+ * 1.  Pointer to the hash table proper. The table is an array of pointers to
+ *     hash nodes (of type hnode_t). If the table is empty, every element of
+ *     this table is a null pointer. A non-null entry points to the first
+ *     element of a chain of nodes.
+ * 2.  This member keeps track of the size of the hash table---that is, the
+ *     number of chain pointers.
+ * 3.  The count member maintains the number of elements that are presently
+ *     in the hash table.
+ * 4.  The maximum count is the greatest number of nodes that can populate this
+ *     table. If the table contains this many nodes, no more can be inserted,
+ *     and the hash_isfull() function returns true.
+ * 5.  The high mark is a population threshold, measured as a number of nodes,
+ *     which, if exceeded, will trigger a table expansion. Only dynamic hash
+ *     tables are subject to this expansion.
+ * 6.  The low mark is a minimum population threshold, measured as a number of
+ *     nodes. If the table population drops below this value, a table shrinkage
+ *     will occur. Only dynamic tables are subject to this reduction.  No table
+ *     will shrink beneath a certain absolute minimum number of nodes.
+ * 7.  This is the a pointer to the hash table's comparison function. The
+ *     function is set once at initialization or creation time.
+ * 8.  Pointer to the table's hashing function, set once at creation or
+ *     initialization time.
+ * 9.  The current hash table mask. If the size of the hash table is 2^N,
+ *     this value has its low N bits set to 1, and the others clear. It is used
+ *     to select bits from the result of the hashing function to compute an
+ *     index into the table.
+ * 10. A flag which indicates whether the table is to be dynamically resized. It
+ *     is set to 1 in dynamically allocated tables, 0 in tables that are
+ *     statically allocated.
+ */
+
+typedef struct hash_t {
+#if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+    struct hnode_t **hash_table;		/* 1 */
+    hashcount_t hash_nchains;			/* 2 */
+    hashcount_t hash_nodecount;			/* 3 */
+    hashcount_t hash_maxcount;			/* 4 */
+    hashcount_t hash_highmark;			/* 5 */
+    hashcount_t hash_lowmark;			/* 6 */
+    hash_comp_t hash_compare;			/* 7 */
+    hash_fun_t hash_function;			/* 8 */
+    hnode_alloc_t hash_allocnode;
+    hnode_free_t hash_freenode;
+    void *hash_context;
+    hash_val_t hash_mask;			/* 9 */
+    int hash_dynamic;				/* 10 */
+#else
+    int hash_dummy;
+#endif
+} hash_t;
+
+/*
+ * Hash scanner structure, used for traversals of the data structure.
+ * Notes:
+ * 1. Pointer to the hash table that is being traversed.
+ * 2. Reference to the current chain in the table being traversed (the chain
+ *    that contains the next node that shall be retrieved).
+ * 3. Pointer to the node that will be retrieved by the subsequent call to
+ *    hash_scan_next().
+ */
+
+typedef struct hscan_t {
+#if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+    hash_t *hash_table;		/* 1 */
+    hash_val_t hash_chain;	/* 2 */
+    hnode_t *hash_next;		/* 3 */
+#else
+    int hash_dummy;
+#endif
+} hscan_t;
+
+extern hash_t *hash_create(hashcount_t, hash_comp_t, hash_fun_t);
+extern void hash_set_allocator(hash_t *, hnode_alloc_t, hnode_free_t, void *);
+extern void hash_destroy(hash_t *);
+extern void hash_free_nodes(hash_t *);
+extern void hash_free(hash_t *);
+extern hash_t *hash_init(hash_t *, hashcount_t, hash_comp_t,
+	hash_fun_t, hnode_t **, hashcount_t);
+extern void hash_insert(hash_t *, hnode_t *, const void *);
+extern hnode_t *hash_lookup(hash_t *, const void *);
+extern hnode_t *hash_delete(hash_t *, hnode_t *);
+extern int hash_alloc_insert(hash_t *, const void *, void *);
+extern void hash_delete_free(hash_t *, hnode_t *);
+
+extern void hnode_put(hnode_t *, void *);
+extern void *hnode_get(hnode_t *);
+extern const void *hnode_getkey(hnode_t *);
+extern hashcount_t hash_count(hash_t *);
+extern hashcount_t hash_size(hash_t *);
+
+extern int hash_isfull(hash_t *);
+extern int hash_isempty(hash_t *);
+
+extern void hash_scan_begin(hscan_t *, hash_t *);
+extern hnode_t *hash_scan_next(hscan_t *);
+extern hnode_t *hash_scan_delete(hash_t *, hnode_t *);
+extern void hash_scan_delfree(hash_t *, hnode_t *);
+
+extern int hash_verify(hash_t *);
+
+extern hnode_t *hnode_create(void *);
+extern hnode_t *hnode_init(hnode_t *, void *);
+extern void hnode_destroy(hnode_t *);
+
+#if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+#ifdef KAZLIB_SIDEEFFECT_DEBUG
+#define hash_isfull(H) (SFX_CHECK(H)->hash_nodecount == (H)->hash_maxcount)
+#else
+#define hash_isfull(H) ((H)->hash_nodecount == (H)->hash_maxcount)
+#endif
+#define hash_isempty(H) ((H)->hash_nodecount == 0)
+#define hash_count(H) ((H)->hash_nodecount)
+#define hash_size(H) ((H)->hash_nchains)
+#define hnode_get(N) ((N)->hash_data)
+#define hnode_getkey(N) ((N)->hash_key)
+#define hnode_put(N, V) ((N)->hash_data = (V))
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libutil/kazlib/list.c b/libutil/kazlib/list.c
new file mode 100644
index 0000000..818b427
--- /dev/null
+++ b/libutil/kazlib/list.c
@@ -0,0 +1,766 @@
+/*
+ * List Abstract Data Type
+ * Copyright (C) 1997 Kaz Kylheku <kaz at ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <assert.h>
+#define LIST_IMPLEMENTATION
+#include "list.h"
+
+#define next list_next
+#define prev list_prev
+#define data list_data
+
+#define pool list_pool
+#define fre list_free
+#define size list_size
+
+#define nilnode list_nilnode
+#define nodecount list_nodecount
+#define maxcount list_maxcount
+
+#define list_nil(L)		(&(L)->nilnode)
+#define list_first_priv(L)	((L)->nilnode.next)
+#define list_last_priv(L)	((L)->nilnode.prev)
+#define lnode_next(N)		((N)->next)
+#define lnode_prev(N)		((N)->prev)
+
+/*
+ * Initialize a list object supplied by the client such that it becomes a valid
+ * empty list. If the list is to be ``unbounded'', the maxcount should be
+ * specified as LISTCOUNT_T_MAX, or, alternately, as -1. The value zero
+ * is not permitted.
+ */
+
+list_t *list_init(list_t *list, listcount_t maxcount)
+{
+    assert (maxcount != 0);
+    list->nilnode.next = &list->nilnode;
+    list->nilnode.prev = &list->nilnode;
+    list->nodecount = 0;
+    list->maxcount = maxcount;
+    return list;
+}
+
+/*
+ * Dynamically allocate a list object using malloc(), and initialize it so that
+ * it is a valid empty list. If the list is to be ``unbounded'', the maxcount
+ * should be specified as LISTCOUNT_T_MAX, or, alternately, as -1.
+ */
+
+list_t *list_create(listcount_t maxcount)
+{
+    list_t *new = malloc(sizeof *new);
+    if (new) {
+	assert (maxcount != 0);
+	new->nilnode.next = &new->nilnode;
+	new->nilnode.prev = &new->nilnode;
+	new->nodecount = 0;
+	new->maxcount = maxcount;
+    }
+    return new;
+}
+
+/*
+ * Destroy a dynamically allocated list object.
+ * The client must remove the nodes first.
+ */
+
+void list_destroy(list_t *list)
+{
+    assert (list_isempty(list));
+    free(list);
+}
+
+/*
+ * Free all of the nodes of a list. The list must contain only 
+ * dynamically allocated nodes. After this call, the list
+ * is empty.
+ */
+
+void list_destroy_nodes(list_t *list)
+{
+    lnode_t *lnode = list_first_priv(list), *nil = list_nil(list), *tmp;
+
+    while (lnode != nil) {
+	tmp = lnode->next;
+	lnode->next = NULL;
+	lnode->prev = NULL;
+	lnode_destroy(lnode);
+	lnode = tmp;
+    }
+
+    list_init(list, list->maxcount);
+}
+
+/*
+ * Return all of the nodes of a list to a node pool. The nodes in
+ * the list must all have come from the same pool.
+ */
+
+void list_return_nodes(list_t *list, lnodepool_t *pool)
+{
+    lnode_t *lnode = list_first_priv(list), *tmp, *nil = list_nil(list);
+
+    while (lnode != nil) {
+	tmp = lnode->next;
+	lnode->next = NULL;
+	lnode->prev = NULL;
+	lnode_return(pool, lnode);
+	lnode = tmp;
+    }
+
+    list_init(list, list->maxcount);
+}
+
+/*
+ * Insert the node ``new'' into the list immediately after ``this'' node.
+ */
+
+void list_ins_after(list_t *list, lnode_t *new, lnode_t *this)
+{
+    lnode_t *that = this->next;
+
+    assert (new != NULL);
+    assert (!list_contains(list, new));
+    assert (!lnode_is_in_a_list(new));
+    assert (this == list_nil(list) || list_contains(list, this));
+    assert (list->nodecount + 1 > list->nodecount);
+
+    new->prev = this;
+    new->next = that;
+    that->prev = new;
+    this->next = new;
+    list->nodecount++;
+
+    assert (list->nodecount <= list->maxcount);
+}
+
+/*
+ * Insert the node ``new'' into the list immediately before ``this'' node.
+ */
+
+void list_ins_before(list_t *list, lnode_t *new, lnode_t *this)
+{
+    lnode_t *that = this->prev;
+
+    assert (new != NULL);
+    assert (!list_contains(list, new));
+    assert (!lnode_is_in_a_list(new));
+    assert (this == list_nil(list) || list_contains(list, this));
+    assert (list->nodecount + 1 > list->nodecount);
+
+    new->next = this;
+    new->prev = that;
+    that->next = new;
+    this->prev = new;
+    list->nodecount++;
+
+    assert (list->nodecount <= list->maxcount);
+}
+
+/*
+ * Delete the given node from the list.
+ */
+
+lnode_t *list_delete(list_t *list, lnode_t *del)
+{
+    lnode_t *next = del->next;
+    lnode_t *prev = del->prev;
+
+    assert (list_contains(list, del));
+
+    prev->next = next;
+    next->prev = prev;
+    list->nodecount--;
+
+    del->next = del->prev = NULL;
+
+    return del;
+}
+
+/*
+ * For each node in the list, execute the given function. The list,
+ * current node and the given context pointer are passed on each
+ * call to the function.
+ */
+
+void list_process(list_t *list, void *context,
+	void (* function)(list_t *list, lnode_t *lnode, void *context))
+{
+    lnode_t *node = list_first_priv(list), *next, *nil = list_nil(list);
+
+    while (node != nil) {
+	/* check for callback function deleting	*/
+	/* the next node from under us		*/
+	assert (list_contains(list, node));
+	next = node->next;
+	function(list, node, context);
+	node = next;
+    }
+}
+
+/*
+ * Dynamically allocate a list node and assign it the given piece of data.
+ */
+
+lnode_t *lnode_create(void *data)
+{
+    lnode_t *new = malloc(sizeof *new);
+    if (new) {
+	new->data = data;
+	new->next = NULL;
+	new->prev = NULL;
+    }
+    return new;
+}
+
+/*
+ * Initialize a user-supplied lnode.
+ */
+
+lnode_t *lnode_init(lnode_t *lnode, void *data)
+{
+    lnode->data = data;
+    lnode->next = NULL;
+    lnode->prev = NULL;
+    return lnode;
+}
+
+/*
+ * Destroy a dynamically allocated node.
+ */
+
+void lnode_destroy(lnode_t *lnode)
+{
+    assert (!lnode_is_in_a_list(lnode));
+    free(lnode);
+}
+
+/*
+ * Initialize a node pool object to use a user-supplied set of nodes.
+ * The ``nodes'' pointer refers to an array of lnode_t objects, containing
+ * ``n'' elements.
+ */
+
+lnodepool_t *lnode_pool_init(lnodepool_t *pool, lnode_t *nodes, listcount_t n)
+{
+    listcount_t i;
+
+    assert (n != 0);
+
+    pool->pool = nodes;
+    pool->fre = nodes;
+    pool->size = n;
+    for (i = 0; i < n - 1; i++) {
+	nodes[i].next = nodes + i + 1;
+    }
+    nodes[i].next = NULL;
+    nodes[i].prev = nodes;	/* to make sure node is marked ``on list'' */
+    return pool;
+}
+
+/*
+ * Create a dynamically allocated pool of n nodes.
+ */
+
+lnodepool_t *lnode_pool_create(listcount_t n)
+{
+    lnodepool_t *pool;
+    lnode_t *nodes;
+
+    assert (n != 0);
+
+    pool = malloc(sizeof *pool);
+    if (!pool)
+	return NULL;
+    nodes = malloc(n * sizeof *nodes);
+    if (!nodes) {
+	free(pool);
+	return NULL;
+    }
+    lnode_pool_init(pool, nodes, n);
+    return pool;
+}
+
+/*
+ * Determine whether the given pool is from this pool.
+ */
+
+int lnode_pool_isfrom(lnodepool_t *pool, lnode_t *node)
+{
+    listcount_t i;
+
+    /* this is carefully coded this way because ANSI C forbids pointers
+       to different objects from being subtracted or compared other
+       than for exact equality */
+
+    for (i = 0; i < pool->size; i++) {
+	if (pool->pool + i == node)
+	    return 1;
+    }
+    return 0;
+}
+
+/*
+ * Destroy a dynamically allocated pool of nodes.
+ */
+
+void lnode_pool_destroy(lnodepool_t *p)
+{
+    free(p->pool);
+    free(p);
+}
+
+/*
+ * Borrow a node from a node pool. Returns a null pointer if the pool
+ * is exhausted. 
+ */
+
+lnode_t *lnode_borrow(lnodepool_t *pool, void *data)
+{
+    lnode_t *new = pool->fre;
+    if (new) {
+	pool->fre = new->next;
+	new->data = data;
+	new->next = NULL;
+	new->prev = NULL;
+    }
+    return new;
+}
+
+/*
+ * Return a node to a node pool. A node must be returned to the pool
+ * from which it came.
+ */
+
+void lnode_return(lnodepool_t *pool, lnode_t *node)
+{
+    assert (lnode_pool_isfrom(pool, node));
+    assert (!lnode_is_in_a_list(node));
+
+    node->next = pool->fre;
+    node->prev = node;
+    pool->fre = node;
+}
+
+/*
+ * Determine whether the given list contains the given node.
+ * According to this function, a list does not contain its nilnode.
+ */
+
+int list_contains(list_t *list, lnode_t *node)
+{
+    lnode_t *n, *nil = list_nil(list);
+
+    for (n = list_first_priv(list); n != nil; n = lnode_next(n)) {
+	if (node == n)
+	    return 1;
+    }
+
+    return 0;
+}
+
+/*
+ * A more generalized variant of list_transfer. This one removes a
+ * ``slice'' from the source list and appends it to the destination
+ * list.
+ */
+
+void list_extract(list_t *dest, list_t *source, lnode_t *first, lnode_t *last)
+{
+    listcount_t moved = 1;
+
+    assert (first == NULL || list_contains(source, first));
+    assert (last == NULL || list_contains(source, last));
+
+    if (first == NULL || last == NULL)
+	return;
+
+    /* adjust the destination list so that the slice is spliced out */
+
+    first->prev->next = last->next;
+    last->next->prev = first->prev;
+
+    /* graft the splice at the end of the dest list */
+
+    last->next = &dest->nilnode;
+    first->prev = dest->nilnode.prev;
+    dest->nilnode.prev->next = first;
+    dest->nilnode.prev = last;
+
+    while (first != last) {
+	first = first->next;
+	assert (first != list_nil(source));	/* oops, last before first! */
+	moved++;
+    }
+    
+    /* assert no overflows */
+    assert (source->nodecount - moved <= source->nodecount);
+    assert (dest->nodecount + moved >= dest->nodecount);
+
+    /* assert no weirdness */
+    assert (moved <= source->nodecount);
+
+    source->nodecount -= moved;
+    dest->nodecount += moved;
+
+    /* assert list sanity */
+    assert (list_verify(source));
+    assert (list_verify(dest));
+}
+
+
+/*
+ * Split off a trailing sequence of nodes from the source list and relocate
+ * them to the tail of the destination list. The trailing sequence begins
+ * with node ``first'' and terminates with the last node of the source
+ * list. The nodes are added to the end of the new list in their original
+ * order.
+ */
+
+void list_transfer(list_t *dest, list_t *source, lnode_t *first)
+{
+    listcount_t moved = 1;
+    lnode_t *last;
+
+    assert (first == NULL || list_contains(source, first));
+
+    if (first == NULL)
+	return;
+
+    last = source->nilnode.prev;
+
+    source->nilnode.prev = first->prev;
+    first->prev->next = &source->nilnode;
+
+    last->next = &dest->nilnode;
+    first->prev = dest->nilnode.prev;
+    dest->nilnode.prev->next = first;
+    dest->nilnode.prev = last;
+
+    while (first != last) {
+	first = first->next;
+	moved++;
+    }
+    
+    /* assert no overflows */
+    assert (source->nodecount - moved <= source->nodecount);
+    assert (dest->nodecount + moved >= dest->nodecount);
+
+    /* assert no weirdness */
+    assert (moved <= source->nodecount);
+
+    source->nodecount -= moved;
+    dest->nodecount += moved;
+
+    /* assert list sanity */
+    assert (list_verify(source));
+    assert (list_verify(dest));
+}
+
+void list_merge(list_t *dest, list_t *sour,
+	int compare (const void *, const void *))
+{
+    lnode_t *dn, *sn, *tn;
+    lnode_t *d_nil = list_nil(dest), *s_nil = list_nil(sour);
+
+    /* Nothing to do if source and destination list are the same. */
+    if (dest == sour)
+	return;
+
+    /* overflow check */
+    assert (list_count(sour) + list_count(dest) >= list_count(sour));
+
+    /* lists must be sorted */
+    assert (list_is_sorted(sour, compare));
+    assert (list_is_sorted(dest, compare));
+
+    dn = list_first_priv(dest);
+    sn = list_first_priv(sour);
+
+    while (dn != d_nil && sn != s_nil) {
+	if (compare(lnode_get(dn), lnode_get(sn)) >= 0) {
+	    tn = lnode_next(sn);
+	    list_delete(sour, sn);
+	    list_ins_before(dest, sn, dn);
+	    sn = tn;
+	} else {
+	    dn = lnode_next(dn);
+	}
+    }
+
+    if (dn != d_nil)
+	return;
+
+    if (sn != s_nil)
+	list_transfer(dest, sour, sn);
+}
+
+void list_sort(list_t *list, int compare(const void *, const void *))
+{
+    list_t extra;
+    listcount_t middle;
+    lnode_t *node;
+
+    if (list_count(list) > 1) {
+	middle = list_count(list) / 2;
+	node = list_first_priv(list);
+
+	list_init(&extra, list_count(list) - middle);
+
+	while (middle--)
+	    node = lnode_next(node);
+	
+	list_transfer(&extra, list, node);
+	list_sort(list, compare);
+	list_sort(&extra, compare);
+	list_merge(list, &extra, compare);
+    } 
+    assert (list_is_sorted(list, compare));
+}
+
+lnode_t *list_find(list_t *list, const void *key, int compare(const void *, const void *))
+{
+    lnode_t *node;
+
+    for (node = list_first_priv(list); node != list_nil(list); node = node->next) {
+	if (compare(lnode_get(node), key) == 0)
+	    return node;
+    }
+    
+    return 0;
+}
+
+
+/*
+ * Return 1 if the list is in sorted order, 0 otherwise
+ */
+
+int list_is_sorted(list_t *list, int compare(const void *, const void *))
+{
+    lnode_t *node, *next, *nil;
+
+    next = nil = list_nil(list);
+    node = list_first_priv(list);
+
+    if (node != nil)
+	next = lnode_next(node);
+
+    for (; next != nil; node = next, next = lnode_next(next)) {
+	if (compare(lnode_get(node), lnode_get(next)) > 0)
+	    return 0;
+    }
+
+    return 1;
+}
+
+/*
+ * Get rid of macro functions definitions so they don't interfere
+ * with the actual definitions
+ */
+
+#undef list_isempty
+#undef list_isfull
+#undef lnode_pool_isempty
+#undef list_append
+#undef list_prepend
+#undef list_first
+#undef list_last
+#undef list_next
+#undef list_prev
+#undef list_count
+#undef list_del_first
+#undef list_del_last
+#undef lnode_put
+#undef lnode_get
+
+/*
+ * Return 1 if the list is empty, 0 otherwise
+ */
+
+int list_isempty(list_t *list)
+{
+    return list->nodecount == 0;
+}
+
+/*
+ * Return 1 if the list is full, 0 otherwise
+ * Permitted only on bounded lists. 
+ */
+
+int list_isfull(list_t *list)
+{
+    return list->nodecount == list->maxcount;
+}
+
+/*
+ * Check if the node pool is empty.
+ */
+
+int lnode_pool_isempty(lnodepool_t *pool)
+{
+    return (pool->fre == NULL);
+}
+
+/*
+ * Add the given node at the end of the list
+ */
+
+void list_append(list_t *list, lnode_t *node)
+{
+    list_ins_before(list, node, &list->nilnode);
+}
+
+/*
+ * Add the given node at the beginning of the list.
+ */
+
+void list_prepend(list_t *list, lnode_t *node)
+{
+    list_ins_after(list, node, &list->nilnode);
+}
+
+/*
+ * Retrieve the first node of the list
+ */
+
+lnode_t *list_first(list_t *list)
+{
+    if (list->nilnode.next == &list->nilnode)
+	return NULL;
+    return list->nilnode.next;
+}
+
+/*
+ * Retrieve the last node of the list
+ */
+
+lnode_t *list_last(list_t *list)
+{
+    if (list->nilnode.prev == &list->nilnode)
+	return NULL;
+    return list->nilnode.prev;
+}
+
+/*
+ * Retrieve the count of nodes in the list
+ */
+
+listcount_t list_count(list_t *list)
+{
+    return list->nodecount;
+}
+
+/*
+ * Remove the first node from the list and return it.
+ */
+
+lnode_t *list_del_first(list_t *list)
+{
+    return list_delete(list, list->nilnode.next);
+}
+
+/*
+ * Remove the last node from the list and return it.
+ */
+
+lnode_t *list_del_last(list_t *list)
+{
+    return list_delete(list, list->nilnode.prev);
+}
+
+
+/*
+ * Associate a data item with the given node.
+ */
+
+void lnode_put(lnode_t *lnode, void *data)
+{
+    lnode->data = data;
+}
+
+/*
+ * Retrieve the data item associated with the node.
+ */
+
+void *lnode_get(lnode_t *lnode)
+{
+    return lnode->data;
+}
+
+/*
+ * Retrieve the node's successor. If there is no successor, 
+ * NULL is returned.
+ */
+
+lnode_t *list_next(list_t *list, lnode_t *lnode)
+{
+    assert (list_contains(list, lnode));
+
+    if (lnode->next == list_nil(list))
+	return NULL;
+    return lnode->next;
+}
+
+/*
+ * Retrieve the node's predecessor. See comment for lnode_next().
+ */
+
+lnode_t *list_prev(list_t *list, lnode_t *lnode)
+{
+    assert (list_contains(list, lnode));
+
+    if (lnode->prev == list_nil(list))
+	return NULL;
+    return lnode->prev;
+}
+
+/*
+ * Return 1 if the lnode is in some list, otherwise return 0.
+ */
+
+int lnode_is_in_a_list(lnode_t *lnode)
+{
+    return (lnode->next != NULL || lnode->prev != NULL);
+}
+
+
+int list_verify(list_t *list)
+{
+    lnode_t *node = list_first_priv(list), *nil = list_nil(list);
+    listcount_t count = list_count(list);
+
+    if (node->prev != nil)
+	return 0;
+
+    if (count > list->maxcount)
+	return 0;
+
+    while (node != nil && count--) {
+	if (node->next->prev != node)
+	    return 0;
+	node = node->next;
+    }
+
+    if (count != 0 || node != nil)
+	return 0;
+    
+    return 1;
+}
diff --git a/libutil/kazlib/list.h b/libutil/kazlib/list.h
new file mode 100644
index 0000000..97abc2f
--- /dev/null
+++ b/libutil/kazlib/list.h
@@ -0,0 +1,152 @@
+/*
+ * List Abstract Data Type
+ * Copyright (C) 1997 Kaz Kylheku <kaz at ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ */
+
+#ifndef LIST_H
+#define LIST_H
+
+#include <limits.h>
+
+#ifdef KAZLIB_SIDEEFFECT_DEBUG
+#include "sfx.h"
+#define LIST_SFX_CHECK(E) SFX_CHECK(E)
+#else
+#define LIST_SFX_CHECK(E) (E)
+#endif
+
+/*
+ * Blurb for inclusion into C++ translation units
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef unsigned long listcount_t;
+#define LISTCOUNT_T_MAX ULONG_MAX
+
+typedef struct lnode_t {
+#if defined(LIST_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+    struct lnode_t *list_next;
+    struct lnode_t *list_prev;
+    void *list_data;
+#else
+    int list_dummy;
+#endif
+} lnode_t;
+
+typedef struct lnodepool_t {
+#if defined(LIST_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+    struct lnode_t *list_pool;
+    struct lnode_t *list_free;
+    listcount_t list_size;
+#else
+    int list_dummy;
+#endif
+} lnodepool_t;
+
+typedef struct list_t {
+#if defined(LIST_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+    lnode_t list_nilnode;
+    listcount_t list_nodecount;
+    listcount_t list_maxcount;
+#else
+    int list_dummy;
+#endif
+} list_t;
+
+lnode_t *lnode_create(void *);
+lnode_t *lnode_init(lnode_t *, void *);
+void lnode_destroy(lnode_t *);
+void lnode_put(lnode_t *, void *);
+void *lnode_get(lnode_t *);
+int lnode_is_in_a_list(lnode_t *);
+
+#if defined(LIST_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+#define lnode_put(N, D)		((N)->list_data = (D))
+#define lnode_get(N)		((N)->list_data)
+#endif
+
+lnodepool_t *lnode_pool_init(lnodepool_t *, lnode_t *, listcount_t);
+lnodepool_t *lnode_pool_create(listcount_t);
+void lnode_pool_destroy(lnodepool_t *);
+lnode_t *lnode_borrow(lnodepool_t *, void *);
+void lnode_return(lnodepool_t *, lnode_t *);
+int lnode_pool_isempty(lnodepool_t *);
+int lnode_pool_isfrom(lnodepool_t *, lnode_t *);
+
+list_t *list_init(list_t *, listcount_t);
+list_t *list_create(listcount_t);
+void list_destroy(list_t *);
+void list_destroy_nodes(list_t *);
+void list_return_nodes(list_t *, lnodepool_t *);
+
+listcount_t list_count(list_t *);
+int list_isempty(list_t *);
+int list_isfull(list_t *);
+int list_contains(list_t *, lnode_t *);
+
+void list_append(list_t *, lnode_t *);
+void list_prepend(list_t *, lnode_t *);
+void list_ins_before(list_t *, lnode_t *, lnode_t *);
+void list_ins_after(list_t *, lnode_t *, lnode_t *);
+
+lnode_t *list_first(list_t *);
+lnode_t *list_last(list_t *);
+lnode_t *list_next(list_t *, lnode_t *);
+lnode_t *list_prev(list_t *, lnode_t *);
+
+lnode_t *list_del_first(list_t *);
+lnode_t *list_del_last(list_t *);
+lnode_t *list_delete(list_t *, lnode_t *);
+
+void list_process(list_t *, void *, void (*)(list_t *, lnode_t *, void *));
+
+int list_verify(list_t *);
+
+#if defined(LIST_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+#define lnode_pool_isempty(P)	((P)->list_free == 0)
+#define list_count(L)		((L)->list_nodecount)
+#define list_isempty(L)		((L)->list_nodecount == 0)
+#define list_isfull(L)		(LIST_SFX_CHECK(L)->list_nodecount == (L)->list_maxcount)
+#define list_next(L, N)		(LIST_SFX_CHECK(N)->list_next == &(L)->list_nilnode ? NULL : (N)->list_next)
+#define list_prev(L, N)		(LIST_SFX_CHECK(N)->list_prev == &(L)->list_nilnode ? NULL : (N)->list_prev)
+#define list_first(L)		list_next(LIST_SFX_CHECK(L), &(L)->list_nilnode)
+#define list_last(L)		list_prev(LIST_SFX_CHECK(L), &(L)->list_nilnode)
+#endif
+
+#if defined(LIST_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+#define list_append(L, N)	list_ins_before(LIST_SFX_CHECK(L), N, &(L)->list_nilnode)
+#define list_prepend(L, N)	list_ins_after(LIST_SFX_CHECK(L), N, &(L)->list_nilnode)
+#define list_del_first(L)	list_delete(LIST_SFX_CHECK(L), list_first(L))
+#define list_del_last(L)	list_delete(LIST_SFX_CHECK(L), list_last(L))
+#endif
+
+/* destination list on the left, source on the right */
+
+void list_extract(list_t *, list_t *, lnode_t *, lnode_t *);
+void list_transfer(list_t *, list_t *, lnode_t *first);
+void list_merge(list_t *, list_t *, int (const void *, const void *));
+void list_sort(list_t *, int (const void *, const void *));
+lnode_t *list_find(list_t *, const void *, int (const void *, const void *));
+int list_is_sorted(list_t *, int (const void *, const void *));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libutil/kazlib/sfx.c b/libutil/kazlib/sfx.c
new file mode 100644
index 0000000..829a53d
--- /dev/null
+++ b/libutil/kazlib/sfx.c
@@ -0,0 +1,1138 @@
+/*
+ * SFX---A utility which tries to determine whether a given C expression
+ * is free of side effects. This can be used for verifying that macros which
+ * expand their arguments more than once are not being accidentally misused.
+ *
+ * Copyright (C) 1999 Kaz Kylheku <kaz at ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ */
+
+#include <ctype.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include "except.h"
+#include "sfx.h"
+#include "hash.h"
+#ifdef KAZLIB_POSIX_THREADS
+#include <pthread.h>
+#endif
+
+/*
+ * Exceptions
+ */
+
+#define SFX_EX		0x34DB9C4A
+#define SFX_SYNERR	1
+
+/*
+ * Cache entry
+ */
+
+typedef struct {
+    hnode_t node;
+    const char *expr;
+    sfx_rating_t eff;
+} sfx_entry_t;
+
+/*
+ * Parsing context structure
+ */
+
+typedef struct {
+    const unsigned char *start;
+    const unsigned char *input;
+    size_t size;
+    sfx_rating_t eff;
+} context_t;
+
+/*
+ * Declarator type: abstract, concrete or both
+ */
+
+typedef enum {
+    decl_abstract, decl_concrete, decl_both
+} decl_t;
+
+static void init_context(context_t *ctx, const unsigned char *expr)
+{
+    ctx->input = ctx->start = expr;
+    ctx->size = strlen((const char *) expr) + 1;
+    ctx->eff = sfx_none;
+}
+
+static void assign_context(context_t *copy, context_t *orig)
+{
+    *copy = *orig;
+}
+
+static void set_effect(context_t *ctx, sfx_rating_t eff)
+{
+    assert (eff == sfx_none || eff == sfx_potential || eff == sfx_certain);
+
+    if (eff > ctx->eff)
+	ctx->eff = eff;
+}
+
+static void reset_effect(context_t *ctx)
+{
+    ctx->eff = sfx_none;
+}
+
+static sfx_rating_t get_effect(context_t *ctx)
+{
+    return ctx->eff;
+}
+
+static int skip_ws(context_t *expr)
+{
+    while (*expr->input != 0 && isspace(*expr->input))
+	expr->input++;
+
+    return (*expr->input == 0);
+}
+
+static int get_next(context_t *expr)
+{
+    int ret = *expr->input;
+    if (ret)
+	expr->input++;
+    return ret;
+}
+
+static int get_next_skip_ws(context_t *expr)
+{
+    if (!skip_ws(expr))
+	return *expr->input++;
+    return 0;
+}
+
+static const unsigned char *get_ptr(context_t *expr)
+{
+    return expr->input;
+}
+
+static void skip_n(context_t *ctx, size_t n)
+{
+    assert ((size_t) (ctx->input - ctx->start) <= ctx->size - n);
+    ctx->input += n;
+}
+
+static void put_back(context_t *expr, int ch)
+{
+    if (ch)
+	expr->input--;
+}
+
+static int peek_next(context_t *expr)
+{
+    return *expr->input;
+}
+
+static void syntax_error(void)
+{
+    except_throw(SFX_EX, SFX_SYNERR, "syntax_error");
+}
+
+static void match_hard(context_t *expr, int match)
+{
+    int ch = get_next(expr);
+    if (ch != match)
+	syntax_error();
+}
+
+static void chk_comma(context_t *);
+
+static void skip_ident(context_t *expr)
+{
+    int ch = get_next(expr);
+
+    if (!isalpha(ch) && ch != '_')
+	syntax_error();
+
+    do {
+	ch = get_next(expr);
+    } while (isalnum(ch) || ch == '_');
+
+    put_back(expr, ch);
+}
+
+static void skip_constant(context_t *expr)
+{
+    int ch = get_next(expr);
+
+    assert (isdigit(ch) || ch == '.');
+
+    do {
+	ch = get_next(expr);
+	if (ch == 'e' || ch == 'E') {
+	    ch = get_next(expr);
+	    if (ch == '+' || ch == '-') {
+		ch = get_next(expr);
+		if (!isdigit(ch))
+		    syntax_error();
+	    }
+	}
+    } while (ch != 0 && (isalnum(ch) || ch == '.'));
+
+    put_back(expr, ch);
+}
+
+static void skip_strlit(context_t *expr)
+{
+    int ch = get_next(expr);
+
+    assert (ch == '"');
+
+    do {
+	ch = get_next(expr);
+	if (ch == '\\') {
+	    get_next(expr);
+	    continue;
+	}
+    } while (ch != 0 && ch != '"');
+
+    if (ch != '"')
+	syntax_error();
+}
+
+static void skip_charlit(context_t *expr)
+{
+    int ch = get_next(expr);
+
+    assert (ch == '\'');
+
+    do {
+	ch = get_next(expr);
+	if (ch == '\\') {
+	    get_next(expr);
+	    continue;
+	}
+    } while (ch != 0 && ch != '\'');
+
+    if (ch != '\'')
+	syntax_error();
+}
+
+static void chk_spec_qual_list(context_t *expr)
+{
+    skip_ws(expr);
+    skip_ident(expr);
+
+    for (;;) {
+	int ch;
+
+	skip_ws(expr);
+	ch = peek_next(expr);
+
+	if (!isalpha(ch) && ch != '_')
+	    break;
+
+	skip_ident(expr);
+    }
+}
+
+static int speculate(void (*chk_func)(context_t *), context_t *expr, context_t *copy, int nextchar)
+{
+    static const except_id_t catch[] = { { SFX_EX, XCEPT_CODE_ANY } };
+    except_t *ex;
+    volatile int result = 0;
+    assign_context(copy, expr);
+
+    except_try_push(catch, 1, &ex);
+
+    if (ex == 0) {
+	chk_func(copy);
+	if (nextchar) {
+	    skip_ws(copy);
+	    match_hard(copy, nextchar);
+	}
+	result = 1;
+    } 
+
+    except_try_pop();
+
+    return result;
+}
+
+static void chk_pointer_opt(context_t *expr)
+{
+    for (;;) {
+	int ch = get_next_skip_ws(expr);
+
+	if (ch != '*') {
+	    put_back(expr, ch);
+	    break;
+	}
+
+	skip_ws(expr);
+
+	ch = peek_next(expr);
+
+	if (ch == '*')
+	    continue;
+	if (!isalpha(ch) && ch != '_')
+	    break;
+
+	skip_ident(expr);
+    }
+}
+
+static void chk_decl(context_t *, decl_t);
+
+static void chk_parm_decl(context_t *expr)
+{
+    chk_spec_qual_list(expr);
+    chk_decl(expr, decl_both);
+}
+
+static void chk_parm_type_list(context_t *expr)
+{
+    for (;;) {
+	int ch;
+
+	chk_parm_decl(expr);
+
+	ch = get_next_skip_ws(expr);
+
+	if (ch != ',') {
+	    put_back(expr, ch);
+	    break;
+	}
+
+	ch = get_next_skip_ws(expr);
+
+	if (ch == '.') {
+	    match_hard(expr, '.');
+	    match_hard(expr, '.');
+	    break;
+	}
+
+	put_back(expr, ch);
+    }
+}
+
+static void chk_conditional(context_t *);
+
+static void chk_direct_decl(context_t *expr, decl_t type)
+{
+    for (;;) {
+	int ch = get_next_skip_ws(expr);
+
+	if (ch == '(') {
+	    skip_ws(expr);
+	    ch = peek_next(expr);
+	    if (ch == '*' || ch == '(' || ch == '[')
+		chk_decl(expr, type);
+	    else if (isalpha(ch) || ch == '_')
+		chk_parm_type_list(expr);
+	    match_hard(expr, ')');
+	} else if (ch == '[') {
+	    skip_ws(expr);
+	    ch = peek_next(expr);
+	    if (ch != ']')
+		chk_conditional(expr);
+	    match_hard(expr, ']');		
+	} else if ((type == decl_concrete || type == decl_both) && (isalpha(ch) || ch == '_')) {
+	    put_back(expr, ch);
+	    skip_ident(expr);
+	    break;
+	} else {
+	    put_back(expr, ch);
+	    break;
+	}
+    }
+}
+
+static void chk_decl(context_t *expr, decl_t type)
+{
+    int ch;
+    chk_pointer_opt(expr);
+    skip_ws(expr);
+    ch = peek_next(expr);
+    if (ch == '[' || ch == '(' || ((type == decl_concrete || type == decl_both) && (isalpha(ch) || ch == '_'))) {
+	chk_direct_decl(expr, type);
+    } 
+}
+
+static void chk_typename(context_t *expr)
+{
+    chk_spec_qual_list(expr);
+    chk_decl(expr, decl_abstract);
+}
+
+static void chk_primary(context_t *expr)
+{
+    int ch = peek_next(expr);
+
+    if (ch == 'L') {
+	get_next(expr);
+	ch = peek_next(expr);
+
+	if (ch == '\'') {
+	    skip_charlit(expr);
+	    return;
+	}
+
+	if (ch == '"') {
+	    skip_strlit(expr);
+	    return;
+	}
+
+	put_back(expr, 'L');
+	ch = 'L';
+    }
+
+    if (isalpha(ch) || ch == '_') {
+    	skip_ident(expr);
+	return;
+    }
+
+    if (isdigit(ch) || ch == '.') {
+	skip_constant(expr);
+	return;
+    }
+
+    if (ch == '(') {
+	get_next(expr);
+	chk_comma(expr);
+	match_hard(expr, ')');
+	return;
+    }
+
+    if (ch == '\'') {
+	skip_charlit(expr);
+	return;
+    }
+
+    if (ch == '"') {
+	skip_strlit(expr);
+	return;
+    }
+
+    syntax_error();
+}
+
+static void chk_postfix(context_t *expr)
+{
+    chk_primary(expr);
+
+    for (;;) {
+	int ch = get_next_skip_ws(expr);
+
+	switch (ch) {
+	case '[':
+	    chk_comma(expr);
+	    skip_ws(expr);
+	    match_hard(expr, ']');
+	    continue;
+	case '(':
+	    set_effect(expr, sfx_potential);
+	    ch = get_next_skip_ws(expr);
+
+	    if (ch != ')') {
+		put_back(expr, ch);
+		/* clever hack: parse non-empty argument list as comma expression */
+		chk_comma(expr);
+		ch = get_next_skip_ws(expr);
+	    }
+
+	    if (ch != ')')
+		syntax_error();
+
+	    continue;
+	case '.':
+	    skip_ws(expr);
+	    skip_ident(expr);
+	    continue;
+	case '-':
+	    ch = get_next(expr);
+
+	    if (ch != '-' && ch != '>') {
+		put_back(expr, ch);
+		put_back(expr, '-');
+		break;
+	    }
+
+	    if (ch == '>') {
+		skip_ws(expr);
+		skip_ident(expr);
+		continue;
+	    }
+
+	    set_effect(expr, sfx_certain);
+	    continue;
+	case '+':
+	    ch = get_next(expr);
+	    if (ch != '+') {
+		put_back(expr, ch);
+		put_back(expr, '+');
+		break;
+	    }
+
+	    set_effect(expr, sfx_certain);
+	    continue;
+	default:
+	    put_back(expr, ch);
+	    break;
+	}
+	break;
+    }
+}
+
+static void chk_cast(context_t *);
+
+static void chk_unary(context_t *expr)
+{
+    for (;;) {
+	int nscan, ch = get_next_skip_ws(expr);
+
+	switch (ch) {
+	case '+':
+	    ch = get_next(expr);
+	    if (ch == '+')
+		set_effect(expr, sfx_certain);
+	    else
+		put_back(expr, ch);
+	    chk_cast(expr);
+	    break;
+	case '-':
+	    ch = get_next(expr);
+	    if (ch == '-')
+		set_effect(expr, sfx_certain);
+	    else
+		put_back(expr, ch);
+	    chk_cast(expr);
+	    break;
+	case '&': case '*': case '~': case '!':
+	    chk_cast(expr);
+	    break;
+	case 's':
+	    put_back(expr, ch);
+	    nscan = 0;
+	    sscanf((const char *) get_ptr(expr), "sizeof%*1[^a-z0-9_]%n", &nscan);
+
+	    if (nscan == 7 || strcmp((const char *) get_ptr(expr), "sizeof") == 0) {
+		sfx_rating_t eff = get_effect(expr);
+
+	    	skip_n(expr, 6);
+
+		ch = get_next_skip_ws(expr);
+
+		if (ch == '(') {
+		    context_t comma, type;
+		    int iscomma = speculate(chk_comma, expr, &comma, ')');
+		    int istype = speculate(chk_typename, expr, &type, ')');
+
+		    if (!iscomma && !istype)
+			syntax_error();
+
+		    if (iscomma) {
+			context_t unary;
+			put_back(expr, ch);
+			if (speculate(chk_unary, expr, &unary, 0)) {
+			    assign_context(expr, &unary);
+			    istype = 0;
+			}
+		    }
+
+		    if (istype)
+			assign_context(expr, &type);
+		} else {
+		    put_back(expr, ch);
+		    chk_unary(expr);
+		}
+
+		reset_effect(expr);
+		set_effect(expr, eff);
+		break;
+	    }
+	    chk_postfix(expr);
+	    break;
+	default:
+	    put_back(expr, ch);
+	    chk_postfix(expr);
+	    break;
+	}
+
+	break;
+    }
+}
+
+static void chk_cast(context_t *expr)
+{
+    enum {
+	parexpr,	/* parenthesized expression */
+	partype,	/* parenthesized type name */
+	parambig,	/* ambiguity between paren expr and paren type name */
+	unary,		/* unary expression */
+	plunary,	/* unary expression with leading plus or minus */
+	other		/* none of the above, or even end of input */
+    } curr = partype, old = partype, peek = partype;
+
+    /* history for backtracking: two cast expression elements back */
+    context_t old_expr = { 0 }, cur_expr = { 0 };
+
+    for (;;) {
+	context_t type, comma, unr;
+	int ch = get_next_skip_ws(expr);
+
+	/*
+	 * Determine what the next bit of input is: parenthesized type name,
+	 * expression, unary expression or what?  Speculative parsing is used
+	 * to test several hypotheses.  For example,  something like
+	 * (X)(Y) ^ 1 is seen, it will be turned, by subsequent iterations of
+	 * this loop, into the codes: parambig, parambig, other.
+	 */
+
+	if (ch == '(') {
+	    int istype = speculate(chk_typename, expr, &type, ')');
+	    int iscomma = speculate(chk_comma, expr, &comma, ')');
+
+	    switch (istype << 1 | iscomma) {
+	    case 0:
+		ch = get_next_skip_ws(expr);
+		if (ch == ')')
+		    peek = other; /* empty parentheses */
+		else
+		    syntax_error();
+		break;
+	    case 1:
+		peek = parexpr;
+		break;
+	    case 2:
+		peek = partype;
+		break;
+	    case 3:
+		peek = parambig;
+		break;
+	    }
+	    put_back(expr, ch);
+	} else if (ch == 0) {
+	    peek = other;
+	} else {
+	    put_back(expr, ch);
+	    if (speculate(chk_unary, expr, &unr, 0)) {
+		peek = (ch == '+' || ch == '-' || ch == '*' || ch == '&') ? plunary : unary;
+	    } else {
+		peek = other;
+	    }
+	}
+
+	/*
+	 * Okay, now we have an idea what is coming in the input. We make some
+	 * sensible decision based on this and the thing we parsed previously.
+	 * Either the parsing continues to grab more parenthesized things, or
+	 * some decision is made to parse out the suffix material sensibly and
+	 * terminate.  Backtracking is used up to two elements back. For
+	 * example in the case of (X)(Y) ^ 1 (parambig, parambig, other) it's
+	 * necessary, upon seeing ^ 1 (other) to go back to second to last
+	 * ambigous parenthesized element (X) and terminate by parsing the
+	 * (X)(Y) as a postfix expression. It cannot be a cast, because ^1
+	 * isn't an expression.  Unary expressions that start with + or -
+	 * create an interesting ambiguity.  Is (X)(Y) + 1 the addition of 1 to
+	 * the result of the call to function X with parameter Y? Or is it the
+	 * unary expression + 1 cast to type Y and X? The safer assumption is
+	 * to go with the function call hypothesis, since that's the
+	 * interpretation that may have side effects. 
+	 */
+
+	switch (curr) {
+	case parexpr:		/* impossible cases */
+	case other:
+	case unary:
+	case plunary:
+	    assert (0);
+	    syntax_error();
+	    /* notreached */
+	case partype:
+	    switch (peek) {
+	    case parexpr:	/* cast in front of parenthesized expression */
+		chk_postfix(expr);
+		return;
+	    case partype:	/* compounding cast: keep looping */
+		break;
+	    case parambig:	/* type or expr: keep looping */
+		break;
+	    case unary:
+	    case plunary:
+		chk_unary(expr);
+		return;
+	    case other:		/* cast in front of non-expression! */
+		syntax_error();
+		/* notreached */
+	    }
+	    break;
+	case parambig:
+	    switch (peek) {
+	    case parexpr:	/* function call */
+		assign_context(expr, &cur_expr);
+		chk_postfix(expr);
+		return;
+	    case partype:	/* compounding cast: keep looping */
+		break;
+	    case parambig:	/* type or expr: keep looping */
+		break;
+	    case unary:
+		chk_unary(expr);
+		return;
+	    case plunary:	/* treat unary expr with + or - as additive */
+	    case other:
+		if (old == parambig) {
+		    /* reparse two expression-like things in a row as call */
+		    assign_context(expr, &old_expr);
+		    chk_postfix(expr);
+		    return;
+		}
+		/* reparse expression followed by non-parenthesized 
+		   stuff as postfix expression */
+		assign_context(expr, &cur_expr);
+		chk_postfix(expr);
+		return;		/* need more context */
+	    }
+	    break;
+	}
+
+	old = curr;
+	curr = peek;
+	assign_context(&old_expr, &cur_expr);
+	assign_context(&cur_expr, expr);
+	assign_context(expr, &type);
+    }
+}
+
+static void chk_multiplicative(context_t *expr)
+{
+    for (;;) {
+	int ch;
+
+	chk_cast(expr);
+	ch = get_next_skip_ws(expr);
+
+	if ((ch != '*' && ch != '/' && ch != '%') || peek_next(expr) == '=') {
+	    put_back(expr, ch);
+	    break;
+	}
+    }
+}
+
+static void chk_additive(context_t *expr)
+{
+    for (;;) {
+	int ch;
+
+	chk_multiplicative(expr);
+	ch = get_next_skip_ws(expr);
+
+	if ((ch != '+' && ch != '-') || peek_next(expr) == '=') {
+	    put_back(expr, ch);
+	    break;
+	}
+    }
+}
+
+static void chk_shift(context_t *expr)
+{
+    for (;;) {
+	int ch;
+
+	chk_additive(expr);
+	ch = get_next_skip_ws(expr);
+
+	if (ch != '<' && ch != '>') {
+	    put_back(expr, ch);
+	    break;
+	}
+
+	if (ch == '<' && peek_next(expr) != '<') {
+	    put_back(expr, ch);
+	    break;
+	}
+
+	if (ch == '>' && peek_next(expr) != '>') {
+	    put_back(expr, ch);
+	    break;
+	}
+
+	get_next(expr);
+
+	if (peek_next(expr) == '=') {
+	    put_back(expr, ch);
+	    put_back(expr, ch);
+	    break;
+	}
+    }
+}
+
+static void chk_relational(context_t *expr)
+{
+    for (;;) {
+	int ch;
+
+	chk_shift(expr);
+	ch = get_next_skip_ws(expr);
+
+	
+	if (ch != '<' && ch != '>') {
+	    put_back(expr, ch);
+	    break;
+	}
+
+	if (ch == '<' && peek_next(expr) == '<') {
+	    put_back(expr, ch);
+	    break;
+	}
+
+	if (ch == '>' && peek_next(expr) == '>') {
+	    put_back(expr, ch);
+	    break;
+	}
+
+	if (peek_next(expr) == '=')
+	    get_next(expr);
+    }
+}
+
+static void chk_equality(context_t *expr)
+{
+    for (;;) {
+	int ch;
+
+	chk_relational(expr);
+	ch = get_next_skip_ws(expr);
+
+	if ((ch != '!' && ch != '=') || peek_next(expr) != '=') {
+	    put_back(expr, ch);
+	    break;
+	}
+
+	match_hard(expr, '=');
+    }
+}
+
+static void chk_and(context_t *expr)
+{
+    for (;;) {
+	int ch;
+
+	chk_equality(expr);
+	ch = get_next_skip_ws(expr);
+
+	if (ch != '&' || peek_next(expr) == '&' || peek_next(expr) == '=') {
+	    put_back(expr, ch);
+	    break;
+	}
+    }
+}
+
+static void chk_exclusive_or(context_t *expr)
+{
+    for (;;) {
+	int ch;
+
+	chk_and(expr);
+	ch = get_next_skip_ws(expr);
+
+	if (ch != '^' || peek_next(expr) == '=') {
+	    put_back(expr, ch);
+	    break;
+	}
+    }
+}
+
+static void chk_inclusive_or(context_t *expr)
+{
+    for (;;) {
+	int ch;
+
+	chk_exclusive_or(expr);
+	ch = get_next_skip_ws(expr);
+
+	if (ch != '|' || peek_next(expr) == '|' || peek_next(expr) == '=') {
+	    put_back(expr, ch);
+	    break;
+	}
+    }
+}
+
+static void chk_logical_and(context_t *expr)
+{
+    for (;;) {
+	int ch;
+
+	chk_inclusive_or(expr);
+	ch = get_next_skip_ws(expr);
+
+	if (ch != '&' || peek_next(expr) != '&') {
+	    put_back(expr, ch);
+	    break;
+	}
+
+	match_hard(expr, '&');
+    }
+}
+
+static void chk_logical_or(context_t *expr)
+{
+    for (;;) {
+	int ch;
+
+	chk_logical_and(expr);
+	ch = get_next_skip_ws(expr);
+
+	if (ch != '|' || peek_next(expr) != '|') {
+	    put_back(expr, ch);
+	    break;
+	}
+
+	match_hard(expr, '|');
+    }
+}
+
+static void chk_conditional(context_t *expr)
+{
+    for (;;) {
+	int ch;
+
+	chk_logical_or(expr);
+	ch = get_next_skip_ws(expr);
+
+	if (ch != '?') {
+	    put_back(expr, ch);
+	    break;
+	}
+
+	chk_comma(expr);
+	
+	skip_ws(expr);
+	match_hard(expr, ':');
+    }
+}
+
+static void chk_assignment(context_t *expr)
+{
+    for (;;) {
+	int ch;
+
+	chk_conditional(expr);
+	ch = get_next_skip_ws(expr);
+
+	switch (ch) {
+	case '=':
+	    break;
+	case '*': case '/': case '%':
+	case '+': case '-': case '&':
+	case '^': case '|':
+	    match_hard(expr, '=');
+	    break;
+	case '<':
+	    match_hard(expr, '<');
+	    match_hard(expr, '=');
+	    break;
+	case '>':
+	    match_hard(expr, '>');
+	    match_hard(expr, '=');
+	    break;
+	case 0:
+	default:
+	    put_back(expr, ch);
+	    return;
+	}
+	set_effect(expr, sfx_certain);
+    }
+}
+
+static void chk_comma(context_t *expr)
+{
+    for (;;) {
+	int ch;
+
+	chk_assignment(expr);
+	ch = get_next_skip_ws(expr);
+
+	if (ch != ',') {
+	    put_back(expr, ch);
+	    break;
+	}
+    }
+}
+
+/*
+ * This function returns 1 if the expression is successfully parsed,
+ * or 0 if there is a syntax error.
+ * 
+ * The object pointed to by eff is set to indicate the side effect ranking of
+ * the parsed expression: sfx_none, sfx_potential and sfx_certain.  These
+ * rankins mean, respectively, that there are no side effects, that there are
+ * potential side effects, or that there certainly are side effects.
+ */
+
+int sfx_determine(const char *expr, sfx_rating_t *eff)
+{
+    static const except_id_t catch[] = { { SFX_EX, XCEPT_CODE_ANY } };
+    except_t *ex;
+    context_t ctx;
+    volatile int retval = 1;
+
+    if (!except_init())
+	return 0;
+
+    init_context(&ctx, (const unsigned char *) expr);
+
+    except_try_push(catch, 1, &ex);
+
+    if (ex == 0) {
+	chk_comma(&ctx);
+	skip_ws(&ctx);
+	if (peek_next(&ctx) != 0)
+	    syntax_error();
+    } else {
+	/* exception caught */
+	retval = 0;
+    }
+
+    except_try_pop();
+
+    *eff = ctx.eff;
+
+    except_deinit();
+
+    return retval;
+}
+
+
+#ifdef KAZLIB_POSIX_THREADS
+
+static pthread_once_t cache_init;
+static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+#define init_once(X, Y) pthread_once(X, Y)
+#define lock_cache() pthread_mutex_lock(&cache_mutex)
+#define unlock_cache() pthread_mutex_unlock(&cache_mutex)
+
+#else
+static int cache_init;
+
+static void init_once(int *once, void (*func)(void))
+{
+    if (*once == 0) {
+	func();
+	*once = 1;
+    }
+}
+
+#define lock_cache()
+#define unlock_cache()
+#endif
+
+static hash_t *cache;
+
+extern hash_t *hash_create(hashcount_t, hash_comp_t, hash_fun_t);
+
+static void init_cache(void)
+{
+    cache = hash_create(HASHCOUNT_T_MAX, 0, 0);
+}
+
+static int lookup_cache(const char *expr, sfx_rating_t *rating)
+{
+    hnode_t *cache_node;
+    init_once(&cache_init, init_cache);
+
+    lock_cache();
+
+    cache_node = hash_lookup(cache, expr);
+
+    unlock_cache();
+
+    if (cache_node != 0) {
+	sfx_entry_t *cache_entry = hnode_get(cache_node);
+	*rating = cache_entry->eff;
+	return 1;
+    }
+
+    return 0;
+}
+
+static int cache_result(const char *expr, sfx_rating_t rating)
+{
+    int result = 0;
+    hnode_t *cache_node;
+
+    init_once(&cache_init, init_cache);
+
+    if (cache == 0)
+	goto bail;
+
+    lock_cache();
+
+    cache_node = hash_lookup(cache, expr);
+
+    if (!cache_node) {
+	sfx_entry_t *cache_entry = malloc(sizeof *cache_entry);
+
+	if (cache_entry == 0)
+	    goto bail_unlock;
+
+	hnode_init(&cache_entry->node, cache_entry);
+	cache_entry->expr = expr;
+	cache_entry->eff = rating;
+	hash_insert(cache, &cache_entry->node, expr);
+    } else {
+	sfx_entry_t *cache_entry = hnode_get(cache_node);
+	cache_entry->eff = rating;
+	result = 1;
+    }
+
+    result = 1;
+
+    
+bail_unlock:
+    unlock_cache();
+
+bail:
+    return result;
+}
+
+
+void sfx_check(const char *expr, const char *file, unsigned long line)
+{
+    sfx_rating_t eff;
+    int success = lookup_cache(expr, &eff);
+
+    if (!success) {
+	success = sfx_determine(expr, &eff);
+	cache_result(expr, eff);
+    }
+
+    if (!success) {
+	fprintf(stderr, "%s:%ld: syntax error in expression \"%s\"\n",
+		file, line, expr);
+    } else if (eff == sfx_potential) {
+	fprintf(stderr, "%s:%ld: expression \"%s\" may have side effects\n",
+		file, line, expr);
+    } else if (eff == sfx_certain) {
+	fprintf(stderr, "%s:%ld: expression \"%s\" has side effects\n",
+		file, line, expr);
+    } else {
+	return;
+    }
+}
+
+int sfx_declare(const char *expr, sfx_rating_t eff)
+{
+    return cache_result(expr, eff);
+}
+
diff --git a/libutil/kazlib/sfx.h b/libutil/kazlib/sfx.h
new file mode 100644
index 0000000..b2a485c
--- /dev/null
+++ b/libutil/kazlib/sfx.h
@@ -0,0 +1,46 @@
+/*
+ * SideChk---A utility which tries to determine whether a given C expression
+ * is free of side effects. This can be used for verifying that macros which
+ * expand their arguments more than once are not being accidentally misused.
+ *
+ * Copyright (C) 1999 Kaz Kylheku <kaz at ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ */
+
+#ifndef SFX_H
+#define SFX_H
+
+#include <assert.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    sfx_none, sfx_potential, sfx_certain
+} sfx_rating_t;
+
+int sfx_determine(const char *, sfx_rating_t *);
+int sfx_declare(const char *, sfx_rating_t);
+void sfx_check(const char *, const char *, unsigned long);
+
+#ifdef __cplusplus
+}
+#endif
+
+#define SFX_CHECK(E) (sfx_check(#E, __FILE__, __LINE__), (E))
+#define SFX_STRING(E) #E
+
+#endif
diff --git a/meryl/args.C b/meryl/args.C
index f1df368..a8573a5 100644
--- a/meryl/args.C
+++ b/meryl/args.C
@@ -430,7 +430,7 @@ merylArgs::merylArgs(int argc, char **argv) {
       personality = 'h';
     } else if (strcmp(argv[arg], "-memory") == 0) {
       arg++;
-      memoryLimit = strtouint64(argv[arg], 0L);
+      memoryLimit = strtouint64(argv[arg], 0L) * 1024 * 1024;
     } else if (strcmp(argv[arg], "-segments") == 0) {
       arg++;
       segmentLimit = strtouint64(argv[arg], 0L);
diff --git a/meryl/build.C b/meryl/build.C
index 70655b6..8bff0fd 100644
--- a/meryl/build.C
+++ b/meryl/build.C
@@ -214,22 +214,12 @@ prepareBatch(merylArgs *args) {
   if (fatalError)
     exit(1);
 
-  if (args->numThreads > 0) {
-    //  If we were given no segment or memory limit, but threads, we
-    //  really want to create n segments.
-    //
-    if ((args->segmentLimit == 0) && (args->memoryLimit == 0)) {
-      args->segmentLimit = args->numThreads;
-    }
+  //  If we were given no segment or memory limit, but threads, we
+  //  really want to create n segments.
+  //
+  if ((args->numThreads > 0) && (args->segmentLimit == 0) && (args->memoryLimit == 0))
+    args->segmentLimit = args->numThreads;
 
-    //  If we are given a memory limit and threads, we want to use that much memory
-    //  total, not per thread.
-    //
-    if ((args->memoryLimit > 0) && (args->numThreads > 0)) {
-      args->segmentLimit = 0;
-      args->memoryLimit /= args->numThreads;
-    }
-  }
 
   {
     seqStream *seqstr = new seqStream(args->inputFile);
@@ -259,53 +249,45 @@ prepareBatch(merylArgs *args) {
 #endif
 
 
-  //  If there is a memory limit, ignore the total number of mers and
-  //  pick a value that fits in memory.
+  //  If there is a memory limit, figure out how to divide the work into an integer multiple of
+  //  numThreads segments.
   //
-  //  Otherwise, if there is a segment limit, split the total number
-  //  of mers into n pieces.  Remember, there cannot be both a
-  //  memoryLimit and a segmentLimit.
+  //  Otherwise, if there is a segment limit, split the total number of mers into n pieces.
   //
   //  Otherwise, we must be doing it all in one fell swoop.
   //
   if (args->memoryLimit) {
     args->mersPerBatch = estimateNumMersInMemorySize(args->merSize, args->memoryLimit, args->positionsEnabled, args->beVerbose);
+
     if (args->mersPerBatch > args->numMersActual)
       args->mersPerBatch = args->numMersActual;
+
+    args->mersPerBatch = (uint64)ceil((double)args->mersPerBatch  / (double)args->numThreads);
     args->segmentLimit = (uint64)ceil((double)args->numMersActual / (double)args->mersPerBatch);
-    if (args->beVerbose)
-      fprintf(stderr, "Have a memory limit: mersPerBatch="uint64FMT" segmentLimit="uint64FMT"\n", args->mersPerBatch, args->segmentLimit);
+
+    args->segmentLimit = args->numThreads * (uint32)ceil((double)args->segmentLimit / (double)args->numThreads);
+
   } else if (args->segmentLimit) {
     args->mersPerBatch = (uint64)ceil((double)args->numMersActual / (double)args->segmentLimit);
-    if (args->beVerbose)
-      fprintf(stderr, "Have a segment limit: mersPerBatch="uint64FMT" segmentLimit="uint64FMT"\n", args->mersPerBatch, args->segmentLimit);
+
   } else {
     args->mersPerBatch = args->numMersActual;
     args->segmentLimit = 1;
-    if (args->beVerbose)
-      fprintf(stderr, "Have NO LIMITS!: mersPerBatch="uint64FMT" segmentLimit="uint64FMT"\n", args->mersPerBatch, args->segmentLimit);
   }
 
   args->basesPerBatch = (uint64)ceil((double)args->numBasesActual / (double)args->segmentLimit);
-  if (args->beVerbose)
-    fprintf(stderr, "basesPerBatch = "uint64FMT"\n", args->basesPerBatch);
 
-  //  Choose the optimal number of buckets to reduce memory usage.
-  //  Yes, this is already done in estimateNumMersInMemorySize() (but
-  //  not saved) and we need to do it for the other cases anyway.
+  //  Choose the optimal number of buckets to reduce memory usage.  Yes, this is already done in
+  //  estimateNumMersInMemorySize() (but not saved) and we need to do it for the other cases anyway.
   //
-  //  We use the number of mers per batch + 1 because we need to store
-  //  the first position after the last mer.  That is, if there are
-  //  two mers, we will store that the first mer is at position 0, the
-  //  second mer is at position 1, and the end of the second mer is at
-  //  position 2.
+  //  We use the number of mers per batch + 1 because we need to store the first position after the
+  //  last mer.  That is, if there are two mers, we will store that the first mer is at position 0,
+  //  the second mer is at position 1, and the end of the second mer is at position 2.
   //
   args->bucketPointerWidth = logBaseTwo64(args->basesPerBatch + 1);
   args->numBuckets_log2    = optimalNumberOfBuckets(args->merSize, args->basesPerBatch, args->positionsEnabled);
   args->numBuckets         = (uint64ONE << args->numBuckets_log2);
   args->merDataWidth       = args->merSize * 2 - args->numBuckets_log2;
-  //args->bucketPointerMask  = uint64MASK(args->numBuckets_log2);
-
 
   if (args->merDataWidth > SORTED_LIST_WIDTH * 64) {
     fprintf(stderr, "  numMersActual      = "uint64FMT"\n", args->numMersActual);
@@ -320,11 +302,15 @@ prepareBatch(merylArgs *args) {
 
   if (args->beVerbose) {
     if (args->memoryLimit)
-      fprintf(stderr, "Computing "uint64FMT" segments using "uint64FMT"MB memory each.\n",
-              args->segmentLimit, args->memoryLimit);
+      fprintf(stderr, "Computing "uint64FMT" segments using "uint32FMT" threads and "uint64FMT"MB memory ("uint64FMT"MB if in one batch).\n",
+              args->segmentLimit, args->numThreads,
+              estimateMemory(args->merSize, args->mersPerBatch, args->positionsEnabled) * args->numThreads,
+              estimateMemory(args->merSize, args->numMersActual, args->positionsEnabled));
     else
-      fprintf(stderr, "Computing "uint64FMT" segments using AS MUCH MEMORY AS NEEDED.\n",
-              args->segmentLimit);
+      fprintf(stderr, "Computing "uint64FMT" segments using "uint32FMT" threads and "uint64FMT"MB memory ("uint64FMT"MB if in one batch).\n",
+              estimateMemory(args->merSize, args->mersPerBatch, args->positionsEnabled) * args->numThreads,
+              estimateMemory(args->merSize, args->numMersActual, args->positionsEnabled));
+
     fprintf(stderr, "  numMersActual      = "uint64FMT"\n", args->numMersActual);
     fprintf(stderr, "  mersPerBatch       = "uint64FMT"\n", args->mersPerBatch);
     fprintf(stderr, "  basesPerBatch      = "uint64FMT"\n", args->basesPerBatch);
diff --git a/meryl/estimate.C b/meryl/estimate.C
index 951d69c..09f5073 100644
--- a/meryl/estimate.C
+++ b/meryl/estimate.C
@@ -7,99 +7,51 @@
 #include "libmeryl.H"
 #include "meryl.H"
 
-//  Takes a memory limit in MB, returns the number of mers that we can
-//  fit in that memory size, assuming optimalNumberOfBuckets() below
-//  uses the same algorithm.
+//  Takes a memory limit in MB, returns the number of mers that we can fit in that memory size,
+//  assuming optimalNumberOfBuckets() below uses the same algorithm.
+//
+//  For each possible number of buckets, try all poissible pointer widths.  First we compute the
+//  number of mers that fit in a bucket pointer table of size 2^t storing N bits in the mer data
+//  table, then we check that the number of mers in the mer data table agrees with the width of the
+//  pointer table.
 //
 uint64
 estimateNumMersInMemorySize(uint32 merSize,
-                            uint32 mem,
+                            uint64 mem,
                             bool   positionsEnabled,
                             bool   beVerbose) {
   uint64 maxN    = 0;
   uint64 bestT   = 0;
 
+  uint64 memLimt    = mem * 8;                                //  Memory limit, in bits.
+  uint64 posPerMer  = (positionsEnabled == false) ? 0 : 32;   //  Positions consume space, if enabled.
+  uint64 tMax       = (merSize > 25) ? 50 : 2 * merSize - 2;  //  Max width of bucket pointer table.
 
-  //  For each possible number of buckets, try all poissible pointer
-  //  widths.  First we compute the number of mers that fit in a
-  //  bucket pointer table of size 2^t storing N bits in the mer data
-  //  table, then we check that the number of mers in the mer data
-  //  table agrees with the width of the pointer table.
-
-
-  //  This is the memory size we are trying to fill, in bits.
-  //
-  uint64 memLimt = ((uint64)mem) << 23;
+  //  t - prefix stored in the bucket pointer table; number of entries in the table
+  //  N - width of a bucket pointer
 
-  //  Positions consume space too, but only if enabled.
-  //
-  uint64 posPerMer = 0;
-  if (positionsEnabled)
-    posPerMer = 32;
+  for (uint64 t=2; t < tMax; t++) {
+    for (uint64 N=1; N<40; N++) {
+      uint64 Nmin = uint64ONE << (N - 1);  //  Minimum number of mers we want to fit in the table
+      uint64 Nmax = uint64ONE << (N);      //  Maximum number of mers that can fit in the table
 
-  //  Limit the number of entries in the bucket pointer table to
-  //  50 bits -- thus, the prefix of each mer is at most 25.
-  //
-  uint32  tMax = 2*merSize - 2;
-  if (tMax > 50)
-    tMax = 50;
+      uint64 bucketsize = (uint64ONE << t) * N;  //  Size, in bits, of the pointer table
 
-  for (uint64 t=2; t < tMax; t++) {
+      uint64 n = (memLimt - bucketsize) / (2*merSize - t + posPerMer);  //  Number of mers we can fit into mer data table.
 
-    //  We need to try all possibilities of N, the width of the
-    //  bucket pointer table === log2(numMers).
-    //
-    //  Increased to 40 bits, so we're valid up to 1 trillion mers.
-    //
-    for (uint64 N=1; N<40; N++) {
-      uint64 Nmin = uint64ONE << (N - 1);
-      uint64 Nmax = uint64ONE << (N);
-
-      //  The size in bits of the bucket pointer table.
-      //
-      uint64 bucketsize = (uint64ONE << t) * N;
-
-      //  If our bucket pointer table size hasn't already blown our
-      //  memory limit, compute the number of mers that we can stuff
-      //  into the list.
-      //
-      if (memLimt > bucketsize) {
-
-        //  The number of mers we can then fit into the mer data table
-        //  is easy to compute.
-        //
-        //  Even though we allocate merDataArray, bucketPointers,
-        //  bucketSizes, we don't use merDataArray until after we
-        //  release bucketSizes, and so we only estimate the maximum
-        //  in core (not allocated) size.
-        //
-        uint64 n = (memLimt - bucketsize) / (2*merSize - t + posPerMer);
-
-        //  We can stop now if our computed number of mers is outside the range that
-        //  the bucket pointer table can address.
-        //
-        if ((Nmin <= n) && (n <= Nmax)) {
-
-          //fprintf(stderr, "prefixSize="uint64FMTW(2)" numMers="uint64FMTW(9)" memory=%.3fMB\n",
-          //        t, n,
-          //        (((uint64ONE << t) * logBaseTwo64(n) + n * (2*merSize - t + posPerMer)) >> 3) / 1048576.0);
-
-          //  Remember the settings with the highest number of mers, if
-          //  more than zero mers.
-          //  
-          if ((n > 0) &&
-              (maxN < n)) {
-            maxN  = n;
-            bestT = t;
-          }
-
-        }
+      if ((memLimt >  bucketsize) &&  //  pointer table small enough to fit in memory
+          (n       >  0)          &&  //  at least some space to store mers
+          (n       <= Nmax)       &&  //  enough space for the mers in the data table
+          (Nmin    <= n)          &&  //  ...but not more than enough space
+          (maxN    <  n)) {           //  this value of t fits more mers that any other seen so far
+        maxN  = n;
+        bestT = t;
       }
     }
   }
 
   if (beVerbose)
-    fprintf(stdout, "Can fit "uint64FMT" mers into table with prefix of "uint64FMT" bits, using %8.3fMB (%8.3fMB for positions)\n",
+    fprintf(stdout, "Can fit "uint64FMT" mers into table with prefix of "uint64FMT" bits, using %.3fMB (%.3fMB for positions)\n",
             maxN,
             bestT,
             (((uint64ONE << bestT) * logBaseTwo64(maxN) + maxN * (2*merSize - bestT + posPerMer)) >> 3) / 1048576.0,
@@ -110,6 +62,37 @@ estimateNumMersInMemorySize(uint32 merSize,
 
 
 
+uint64
+estimateMemory(uint32 merSize,
+               uint64 numMers,
+               bool   positionsEnabled) {
+
+  uint64 posPerMer = (positionsEnabled == false) ? 0 : 32;
+  uint64 tMax      = (merSize > 25) ? 50 : 2 * merSize - 2;
+
+  uint64  tMin   = tMax;
+  uint64  memMin = UINT64_MAX;
+
+  for (uint64 t=2; t < tMax; t++) {
+    uint64  N       = logBaseTwo64(numMers);  //  Width of the bucket pointer table
+    uint64  memUsed = ((uint64ONE << t) * logBaseTwo64(numMers) + numMers * (2 * merSize - t + posPerMer)) >> 3;
+
+    if (memUsed < memMin) {
+      tMin   = t;
+      memMin = memUsed;
+    }
+
+    //fprintf(stderr, "t=%2lu N=%2lu memUsed=%16lu -- tMin=%2lu memMin=%16lu\n",
+    //        t, N, memUsed, tMin, memMin);
+  }
+
+  return(memMin >> 20);
+}
+
+
+
+
+
 uint32
 optimalNumberOfBuckets(uint32 merSize,
                        uint64 numMers,
@@ -174,8 +157,7 @@ estimate(merylArgs *args) {
   }
 
   uint32 opth = optimalNumberOfBuckets(args->merSize, args->numMersEstimated, args->positionsEnabled);
-  uint64 memu = ((uint64ONE << opth)    * logBaseTwo64(args->numMersEstimated+1) +
-                 args->numMersEstimated * (2 * args->merSize - opth));
+  uint64 memu = ((uint64ONE << opth) * logBaseTwo64(args->numMersEstimated+1) + args->numMersEstimated * (2 * args->merSize - opth));
 
   fprintf(stderr, uint64FMT" "uint32FMT"-mers can be computed using "uint64FMT"MB memory.\n",
           args->numMersEstimated, args->merSize, memu >> 23);
diff --git a/meryl/kmer-mask.C b/meryl/kmer-mask.C
index ed4b463..daa05bc 100644
--- a/meryl/kmer-mask.C
+++ b/meryl/kmer-mask.C
@@ -34,7 +34,7 @@ public:
     a4[0]     = 0;
 
     aLength   = 0;
-    aRetained = 0.0;
+    aMasked   = 0.0;
     aLabel    = 0;
 
     b2        = alloc + 4 * maxLength;
@@ -50,7 +50,7 @@ public:
     b4[0]     = 0;
 
     bLength   = 0;
-    bRetained = 0.0;
+    bMasked   = 0.0;
     bLabel    = 0;
   };
 
@@ -72,7 +72,7 @@ public:
       fgets(a4, maxLength, FASTQ1);  chomp(a4);
 
       aLength   = strlen(a2);
-      aRetained = 0.0;
+      aMasked   = 0.0;
       aLabel    = 0;
 
       if (a2[maxLength - 2] != 0)
@@ -86,7 +86,7 @@ public:
       fgets(b4, maxLength, FASTQ2);  chomp(b4);
 
       bLength   = strlen(b2);
-      bRetained = 0.0;
+      bMasked   = 0.0;
       bLabel    = 0;
 
       if (b2[maxLength - 2] != 0)
@@ -107,10 +107,10 @@ public:
   void          write(FILE *FASTQ1, FILE *FASTQ2) {
 
     if (FASTQ1)
-      fprintf(FASTQ1, "%s fractionRetained=%.3f\n%s\n%s\n%s\n", a1, aRetained, am, a3, a4);
+      fprintf(FASTQ1, "%s fractionMasked=%.3f\n%s\n%s\n%s\n", a1, aMasked, am, a3, a4);
 
     if (FASTQ2)
-      fprintf(FASTQ2, "%s fractionRetained=%.3f\n%s\n%s\n%s\n", b1, bRetained, bm, b3, b4);
+      fprintf(FASTQ2, "%s fractionMasked=%.3f\n%s\n%s\n%s\n", b1, bMasked, bm, b3, b4);
   };
 
 
@@ -127,7 +127,7 @@ public:
   char         *a4;
   
   uint32        aLength;
-  double        aRetained;
+  double        aMasked;
   uint32        aLabel;
 
   char          b1[1024];
@@ -138,7 +138,7 @@ public:
   char         *b4;
 
   uint32        bLength;
-  double        bRetained;
+  double        bMasked;
   uint32        bLabel;
 };
 
@@ -161,15 +161,16 @@ public:
     existName       = NULL;
     minSize         = 0;
     extend          = 0;
-    keepNovel       = false;
-    keepConfirmed   = false;
 
-    demote          = false;
-    promote         = false;
+    cleaner         = false;
+    dirtier         = false;
     discard         = true;
+    unlink          = false;
 
-    lowThreshold    = 1. / 3.;
-    highThreshold   = 2. / 3.;
+    noMasking       = false;
+
+    cleanThreshold  = 1. / 3.;
+    matchThreshold  = 2. / 3.;
 
     for (uint32 ii=0; ii<1001; ii++)
       scoreHistogram[ii] = 0;
@@ -215,15 +216,16 @@ public:
   char         *existName;
   uint32        minSize;
   uint32        extend;
-  bool          keepNovel;
-  bool          keepConfirmed;
 
-  bool          demote;
-  bool          promote;
+  bool          cleaner;
+  bool          dirtier;
   bool          discard;
+  bool          unlink;
+
+  bool          noMasking;
 
-  double        lowThreshold;
-  double        highThreshold;
+  double        cleanThreshold;
+  double        matchThreshold;
 
   uint32        scoreHistogram[1001];
   uint64        thresholdCounts[4][4];
@@ -274,7 +276,7 @@ printBits(char *S, uint32 Slen, char *found, char *display, const char *label) {
 
 //  Scan the read for kmers that exist in the DB.  Set a bit for each kmer that exists.
 void
-buildMask(char *S, uint32 Slen, char *found, bool keepNovel, existDB *exist, uint32 merSize) {
+buildMask(char *S, uint32 Slen, char *found, existDB *exist, uint32 merSize) {
   merStream    MS(new kMerBuilder(merSize),
                   new seqStream(S, Slen),
                   true, true);
@@ -353,23 +355,25 @@ convertToBases(char *S, uint32 Slen, char *found, uint32 merSize, uint32 extend)
 
 
 //  Assumes the found[] array represents base-based masking.
-//  Returns the fraction of the sequence that is not masked.
+//  Returns the fraction of the sequence that is masked.
 double
-maskSequence(char *S, uint32 Slen, char *found, bool keepNovel, char *display) {
-  uint32  saved  = 0;
+maskSequence(char *S, uint32 Slen, char *found, bool noMasking, char *display) {
+  uint32  masked  = 0;
+
+  //  Flip the sense of the found[ii] test to mask kmers found in the database (true) or missing (false).
 
   for (uint32 ii=0; ii<Slen; ii++) {
-    if (found[ii] == keepNovel) {
-      display[ii] = 'n';
+    if (found[ii] == true) {
+      display[ii] = (noMasking == false) ? 'n' : S[ii];
+      masked++;
     } else {
       display[ii] = S[ii];
-      saved++;
     }
   }
 
   display[Slen] = 0;
 
-  return((double)saved  / Slen);
+  return((double)masked  / Slen);
 }
 
 
@@ -399,8 +403,8 @@ maskWorker(void *G, void *T, void *S) {
   //maskThread  *t = (maskThread *)T;
   fastqRecord  *s = (fastqRecord *)S;
 
-  buildMask(s->a2, s->aLength, s->af, g->keepNovel, g->exist, g->merSize);
-  buildMask(s->b2, s->bLength, s->bf, g->keepNovel, g->exist, g->merSize);
+  buildMask(s->a2, s->aLength, s->af, g->exist, g->merSize);
+  buildMask(s->b2, s->bLength, s->bf, g->exist, g->merSize);
   //printBits(S, found, display , "INITIAL");
 
   removeIsolatedMers(s->a2, s->aLength, s->af, g->minSize);
@@ -411,25 +415,29 @@ maskWorker(void *G, void *T, void *S) {
   convertToBases(s->b2, s->bLength, s->bf, g->merSize, g->extend);
   //printBits(S, found, display, "BASE COVERAGE");
 
-  s->aRetained = maskSequence(s->a2, s->aLength, s->af, g->keepNovel, s->am);
-  s->bRetained = maskSequence(s->b2, s->bLength, s->bf, g->keepNovel, s->bm);
+  s->aMasked = maskSequence(s->a2, s->aLength, s->af, g->noMasking, s->am);
+  s->bMasked = maskSequence(s->b2, s->bLength, s->bf, g->noMasking, s->bm);
 
-  s->aLabel = (s->aRetained < g->lowThreshold) ? 0 : ((s->aRetained < g->highThreshold) ? 1 : 2);
-  s->bLabel = (s->bRetained < g->lowThreshold) ? 0 : ((s->bRetained < g->highThreshold) ? 1 : 2);
+  s->aLabel = (s->aMasked < g->cleanThreshold) ? 0 : ((s->aMasked < g->matchThreshold) ? 1 : 2);
+  s->bLabel = (s->bMasked < g->cleanThreshold) ? 0 : ((s->bMasked < g->matchThreshold) ? 1 : 2);
 
-  if ((s->aLabel != s->bLabel) && (g->demote)) {
-    s->aLabel = MIN(s->aLabel, s->bLabel);
-    s->bLabel = MIN(s->aLabel, s->bLabel);
-  }
+  //  If both reads exist, adjust labels.
 
-  if ((s->aLabel != s->bLabel) && (g->promote)) {
-    s->aLabel = MAX(s->aLabel, s->bLabel);
-    s->bLabel = MAX(s->aLabel, s->bLabel);
-  }
+  if ((s->aLength > 0) && (s->bLength > 0)) {
+    if ((s->aLabel != s->bLabel) && (g->dirtier)) {
+      s->aLabel = MIN(s->aLabel, s->bLabel);
+      s->bLabel = MIN(s->aLabel, s->bLabel);
+    }
 
-  if ((s->aLabel != s->bLabel) && (g->discard)) {
-    s->aLabel = 3;
-    s->bLabel = 3;
+    if ((s->aLabel != s->bLabel) && (g->cleaner)) {
+      s->aLabel = MAX(s->aLabel, s->bLabel);
+      s->bLabel = MAX(s->aLabel, s->bLabel);
+    }
+
+    if ((s->aLabel != s->bLabel) && (g->discard)) {
+      s->aLabel = 3;
+      s->bLabel = 3;
+    }
   }
 }
 
@@ -443,27 +451,24 @@ fastqWriter(void *G, void *S) {
 
   g->thresholdCounts[s->aLabel][s->bLabel]++;
 
-  g->scoreHistogram[(uint32)(1000 * s->aRetained)]++;
-  g->scoreHistogram[(uint32)(1000 * s->bRetained)]++;
+  g->scoreHistogram[(uint32)(1000 * s->aMasked)]++;
+  g->scoreHistogram[(uint32)(1000 * s->bMasked)]++;
 
   delete s;
 }
 
 
-
-
-
-
 FILE *
 openInput(char *filename, bool &P) {
   char  C[2 * FILENAME_MAX];
   FILE *F = NULL;
-  int32 L = strlen(filename);
+  int32 L = (filename == NULL) ? 0 : strlen(filename);
 
-  if ((L > 6) && (strcmp(filename + L - 6, ".fastq") == 0)) {
-    F = fopen(filename, "r");
-    P = false;
-  }
+  if (filename == NULL)
+    return(NULL);
+
+  if (fileExists(filename) == false)
+    fprintf(stderr, "Failed to open Input file '%s' for reading: File not found.\n", filename), exit(1);
 
   if ((L > 3) && (strcmp(filename + L - 3, ".gz") == 0)) {
     sprintf(C, "gzip -dc %s", filename);
@@ -471,28 +476,39 @@ openInput(char *filename, bool &P) {
     P = true;
   }
 
-  if ((L > 4) && (strcmp(filename + L - 4, ".bz2") == 0)) {
+  else if ((L > 4) && (strcmp(filename + L - 4, ".bz2") == 0)) {
     sprintf(C, "bzip2 -dc %s", filename);
     F = popen(C, "r");
     P = true;
   }
 
-  if ((L > 3) && (strcmp(filename + L - 3, ".xz") == 0)) {
+  else if ((L > 3) && (strcmp(filename + L - 3, ".xz") == 0)) {
     sprintf(C, "xz -dc %s", filename);
     F = popen(C, "r");
     P = true;
   }
 
+  else {
+    errno = 0;
+    F = fopen(filename, "r");
+    if (errno)
+      fprintf(stderr, "Failed to open input file '%s' for reading: %s\n", filename, strerror(errno)), exit(1);
+    P = false;
+  }
+
   return(F);
 }
 
+
 void
 closeInput(FILE *F, char *filename, bool P) {
-  if (F)
-    if (P)
-      pclose(F);
-    else
-      fclose(F);
+  if (F == NULL)
+    return;
+
+  if (P)
+    pclose(F);
+  else
+    fclose(F);
 }
 
 
@@ -502,6 +518,8 @@ openOutput(char *prefix, const char *extension) {
   char  N[FILENAME_MAX];
   FILE *F = NULL;
 
+  errno = 0;
+
   sprintf(N, "%s.%s.fastq", prefix, extension);
   F = fopen(N, "w");
   if (errno)
@@ -510,6 +528,7 @@ openOutput(char *prefix, const char *extension) {
   return(F);
 }
 
+
 void
 closeOutput(FILE *F, char *prefix, const char *extension) {
   if (F)
@@ -559,27 +578,40 @@ main(int argc, char **argv) {
     } else if (strcmp(argv[arg], "-v") == 0) {
       beVerbose = true;
 
-    } else if (strcmp(argv[arg], "-novel") == 0) {
-      g->keepNovel = true;
-    } else if (strcmp(argv[arg], "-confirmed") == 0) {
-      g->keepConfirmed = true;
+    } else if (strcmp(argv[arg], "-cleaner") == 0) {
+      g->dirtier = false;
+      g->cleaner = true;
+      g->discard = false;
+      g->unlink  = false;
+
+    } else if (strcmp(argv[arg], "-dirtier") == 0) {
+      g->dirtier = true;
+      g->cleaner = false;
+      g->discard = false;
+      g->unlink  = false;
 
-    } else if (strcmp(argv[arg], "-demote") == 0) {
-      g->demote = true;
-    } else if (strcmp(argv[arg], "-promote") == 0) {
-      g->promote = true;
     } else if (strcmp(argv[arg], "-discard") == 0) {
+      g->dirtier = false;
+      g->cleaner = false;
       g->discard = true;
+      g->unlink  = false;
+
+    } else if (strcmp(argv[arg], "-unlink") == 0) {
+      g->dirtier = false;
+      g->cleaner = false;
+      g->discard = false;
+      g->unlink  = true;
 
-    } else if (strncmp(argv[arg], "-lowthreshold", 3) == 0) {
-      g->lowThreshold = atof(argv[++arg]);
-    } else if (strncmp(argv[arg], "-highthreshold", 3) == 0) {
-      g->highThreshold = atof(argv[++arg]);
+    } else if (strcmp(argv[arg], "-nomasking") == 0) {
+      g->noMasking = true;
+
+    } else if (strncmp(argv[arg], "-clean", 3) == 0) {
+      g->cleanThreshold = atof(argv[++arg]);
+    } else if (strncmp(argv[arg], "-match", 3) == 0) {
+      g->matchThreshold = atof(argv[++arg]);
 
     } else if (strcmp(argv[arg], "-h") == 0) {
       g->outputHistogram = argv[++arg];
-      //} else if (strcmp(argv[arg], "-o") == 0) {
-      //  outputSequence = atoi(argv[++arg]);
 
     } else {
       err++;
@@ -587,66 +619,88 @@ main(int argc, char **argv) {
 
     arg++;
   }
-  if ((g->keepNovel == false) && (g->keepConfirmed == false))
+  if ((g->outPrefix == NULL) && (g->seq1Name != NULL))
+    err++;
+  if ((g->merName == NULL) && (g->existName == NULL))
     err++;
   if (err) {
-    fprintf(stderr, "usage: %s [-novel | -confirmed] ...\n", argv[0]);
+    fprintf(stderr, "usage: %s -mdb mer-database -ms mer-size ...\n", argv[0]);
+    fprintf(stderr, "\n");
+    fprintf(stderr, "INPUTS:\n");
+    fprintf(stderr, "\n");
     fprintf(stderr, "  -mdb mer-database      load masking kmers from meryl 'mer-database'\n");
-    fprintf(stderr, "  -ms  mer-size          \n");
-    fprintf(stderr, "  -edb exist-database    save masking kmers to 'exist-database' for faster restarts\n");
+    fprintf(stderr, "  -ms  mer-size          the mer size used to generate the meryl database\n");
+    fprintf(stderr, "  -edb exist-database    save masking kmers to 'exist-database', and reuse on\n");
+    fprintf(stderr, "                           future runs (optional)\n");
     fprintf(stderr, "\n");
     fprintf(stderr, "  -1 in.1.fastq          input reads - fastq, fastq.gz, fastq.bz2 or fastq.xz\n");
-    fprintf(stderr, "  -2 in.2.fastq                      - (optional, but if not present, messes up the output classification)\n");
+    fprintf(stderr, "  -2 in.2.fastq                      - optional, for mated reads\n");
+    fprintf(stderr, "\n");
+    fprintf(stderr, "  -l length              maximum length of input read (%u)\n", g->maxLength);
+    fprintf(stderr, "                           If too small, program will fail.\n");
+    fprintf(stderr, "                           If too large, program will use excessive memory.\n");
+    fprintf(stderr, "\n");
+    fprintf(stderr, "THRESHOLDS and OPTIONS:\n");
     fprintf(stderr, "\n");
-    fprintf(stderr, "  -o  out                output reads:\n");
-    fprintf(stderr, "                            out.fullymasked.[12].fastq      - reads with below 'lowthreshold' bases retained\n");
-    fprintf(stderr, "                            out.partiallymasked.[12].fastq  - reads in between\n");
-    fprintf(stderr, "                            out.retained.[12].fastq         - reads with more than 'hightreshold' bases retained\n");
-    fprintf(stderr, "                            out.discarded.[12].fastq        - reads with conflicting status\n");
+    fprintf(stderr, "  A read with fewer than 'c' bases masked is 'clean', while a read with more than 'd' bases\n");
+    fprintf(stderr, "  masked is 'match'ed.  Reads in between are 'murky'.  See OUTPUTS.\n");
+    fprintf(stderr, "\n");
+    fprintf(stderr, "  -clean c               mark reads with less than this fraction masked as 'clean' (%.4f)\n", g->cleanThreshold);
+    fprintf(stderr, "  -match d               mark reads with more than this fraction masked as 'match' (%.4f)\n", g->matchThreshold);
     fprintf(stderr, "\n");
     fprintf(stderr, "  -m min-size            ignore database hits below this many consecutive kmers (%d)\n", g->minSize);
     fprintf(stderr, "  -e extend-size         extend database hits across this many missing kmers (%d)\n", g->extend);
     fprintf(stderr, "\n");
-    fprintf(stderr, "  -novel                 RETAIN novel sequence not present in the database\n");
-    fprintf(stderr, "  -confirmed             RETAIN confirmed sequence present in the database\n");
+    fprintf(stderr, "  For mate pairs, how to handle reads with different classifications:\n");
+    fprintf(stderr, "\n");
+    fprintf(stderr, "  -cleaner               use the cleaner classification of the two reads\n");
+    fprintf(stderr, "  -dirtier               use the dirtier classification of the two reads\n");
+    fprintf(stderr, "  -discard               discard pairs with conflicting classifications (DEFAULT)\n");
+    fprintf(stderr, "  -unlink                leave conflicting status alone, output reads to different files\n");
+    fprintf(stderr, "                           NOTE: mate pairing will be broken\n");
     fprintf(stderr, "\n");
-    fprintf(stderr, "  -promote               promote the lesser RETAINED read to the status of the more RETAINED read\n");
-    fprintf(stderr, "                           read1=fullymasked and read2=partiallymasked -> both are partiallymasked\n");
-    fprintf(stderr, "  -demote                demote the more RETAINED read to the status of the lesser RETAINED read\n");
-    fprintf(stderr, "                           read1=fullymasked and read2=partiallymasked -> both are fullymasked\n");
-    fprintf(stderr, "  -discard               discard pairs with conflicting status (DEFAULT)\n");
-    fprintf(stderr, "                           read1=fullymasked and read2=partiallymasked -> both are discarded\n");
+    fprintf(stderr, "  -nomasking             do not trim masked sequence; output the original read\n");
     fprintf(stderr, "\n");
-    fprintf(stderr, "stats on stderr, number of sequences with amount RETAINED:\n");
-    fprintf(stderr, "  -lowthreshold t        (%.4f)\n", g->lowThreshold);
-    fprintf(stderr, "  -highthreshold t       (%.4f)\n", g->highThreshold);
+    fprintf(stderr, "OUTPUTS:\n");
     fprintf(stderr, "\n");
+    fprintf(stderr, "  -o  prefix                output reads:\n");
+    fprintf(stderr, "                            prefix.clean.[12].fastq  - clean (unmasked) reads\n");
+    fprintf(stderr, "                            prefix.murky.[12].fastq  - reads in between\n");
+    fprintf(stderr, "                            prefix.match.[12].fastq  - matching (masked) reads\n");
+    fprintf(stderr, "                            prefix.mixed.[12].fastq  - reads with conflicting status (for mated reads)\n");
     fprintf(stderr, "  -h histogram           write a histogram of the amount of sequence RETAINED\n");
     fprintf(stderr, "\n");
+    fprintf(stderr, "COMPUTE CONFIGURATION and LOGGING\n");
+    fprintf(stderr, "\n");
     fprintf(stderr, "  -t t                   use 't' compute threads\n");
     fprintf(stderr, "  -v                     show progress\n");
 
-    if ((g->keepNovel == false) && (g->keepConfirmed == false))
-      fprintf(stderr, "ERROR: exactly one of -novel and -confirmed must be supplied.\n");
+    if ((g->outPrefix == NULL) && (g->seq1Name != NULL))
+      fprintf(stderr, "ERROR: an output prefix (-o) must be supplied.\n");
+    if ((g->merName == NULL) && (g->existName == NULL))
+      fprintf(stderr, "ERROR: either a mer-database (-mdb) or an exist-database (-edb) must be supplied.\n");
 
     exit(1);
   }
 
-  //  Open inputs
+  //  Open inputs and outputs
 
   g->FASTQ1     = openInput(g->seq1Name, g->FASTQ1pipe);
   g->FASTQ2     = openInput(g->seq2Name, g->FASTQ2pipe);
 
-  g->OUTPUT1[0] = openOutput(g->outPrefix, "fullymasked.1");
-  g->OUTPUT1[1] = openOutput(g->outPrefix, "partiallymasked.1");
-  g->OUTPUT1[2] = openOutput(g->outPrefix, "retained.1");
-  g->OUTPUT1[3] = openOutput(g->outPrefix, "discarded.1");
-
-  g->OUTPUT2[0] = openOutput(g->outPrefix, "fullymasked.2");
-  g->OUTPUT2[1] = openOutput(g->outPrefix, "partiallymasked.2");
-  g->OUTPUT2[2] = openOutput(g->outPrefix, "retained.2");
-  g->OUTPUT2[3] = openOutput(g->outPrefix, "discarded.2");
+  if (g->FASTQ1) {
+    g->OUTPUT1[0] = openOutput(g->outPrefix, "clean.1");      //  Label == 0
+    g->OUTPUT1[1] = openOutput(g->outPrefix, "murky.1");      //  Label == 1
+    g->OUTPUT1[2] = openOutput(g->outPrefix, "match.1");      //  Label == 2
+    g->OUTPUT1[3] = openOutput(g->outPrefix, "mixed.1");      //  Label == 3
+  }
 
+  if (g->FASTQ2) {
+    g->OUTPUT2[0] = openOutput(g->outPrefix, "clean.2");
+    g->OUTPUT2[1] = openOutput(g->outPrefix, "murky.2");
+    g->OUTPUT2[2] = openOutput(g->outPrefix, "match.2");
+    g->OUTPUT2[3] = openOutput(g->outPrefix, "mixed.2");
+  }
 
   //  Load data
 
@@ -669,47 +723,61 @@ main(int argc, char **argv) {
 
   //  Process!
 
-  sweatShop *ss = new sweatShop(fastqLoader, maskWorker, fastqWriter);
+  if (g->FASTQ1) {
+    sweatShop *ss = new sweatShop(fastqLoader, maskWorker, fastqWriter);
 
-  ss->setNumberOfWorkers(numWorkers);
+    ss->setNumberOfWorkers(numWorkers);
 
-  ss->setWorkerBatchSize(1024);
+    ss->setWorkerBatchSize(1024);
 
-  ss->setLoaderQueueSize(numWorkers * 81920);
-  ss->setWriterQueueSize(numWorkers * 81920);
+    ss->setLoaderQueueSize(numWorkers * 81920);
+    ss->setWriterQueueSize(numWorkers * 81920);
 
-  ss->run(g, beVerbose);
+    ss->run(g, beVerbose);
 
-  closeInput(g->FASTQ1, g->seq1Name, g->FASTQ1pipe);
-  closeInput(g->FASTQ2, g->seq1Name, g->FASTQ2pipe);
+    closeInput(g->FASTQ1, g->seq1Name, g->FASTQ1pipe);
+    closeInput(g->FASTQ2, g->seq1Name, g->FASTQ2pipe);
 
-  closeOutput(g->OUTPUT1[0], g->outPrefix, "fulymasked.1");
-  closeOutput(g->OUTPUT1[1], g->outPrefix, "partiallymasked.1");
-  closeOutput(g->OUTPUT1[2], g->outPrefix, "retained.1");
-  closeOutput(g->OUTPUT1[3], g->outPrefix, "discarded.1");
+    closeOutput(g->OUTPUT1[0], g->outPrefix, "clean.1");
+    closeOutput(g->OUTPUT1[1], g->outPrefix, "murky.1");
+    closeOutput(g->OUTPUT1[2], g->outPrefix, "match.1");
+    closeOutput(g->OUTPUT1[3], g->outPrefix, "mixed.1");
 
-  closeOutput(g->OUTPUT2[0], g->outPrefix, "fulymasked.2");
-  closeOutput(g->OUTPUT2[1], g->outPrefix, "partiallymasked.2");
-  closeOutput(g->OUTPUT2[2], g->outPrefix, "retained.2");
-  closeOutput(g->OUTPUT2[3], g->outPrefix, "discarded.2");
+    closeOutput(g->OUTPUT2[0], g->outPrefix, "clean.2");
+    closeOutput(g->OUTPUT2[1], g->outPrefix, "murky.2");
+    closeOutput(g->OUTPUT2[2], g->outPrefix, "match.2");
+    closeOutput(g->OUTPUT2[3], g->outPrefix, "mixed.2");
 
-  fprintf(stderr, "            bBelow    bNormal   bHigh     bDiscarded\n");
-  fprintf(stderr, "aBelow      %8lu  %8lu  %8lu  %8lu\n", g->thresholdCounts[0][0], g->thresholdCounts[0][1], g->thresholdCounts[0][2], g->thresholdCounts[0][3]);
-  fprintf(stderr, "aNormal     %8lu  %8lu  %8lu  %8lu\n", g->thresholdCounts[1][0], g->thresholdCounts[1][1], g->thresholdCounts[1][2], g->thresholdCounts[1][3]);
-  fprintf(stderr, "aHigh       %8lu  %8lu  %8lu  %8lu\n", g->thresholdCounts[2][0], g->thresholdCounts[2][1], g->thresholdCounts[2][2], g->thresholdCounts[2][3]);
-  fprintf(stderr, "aDiscarded  %8lu  %8lu  %8lu  %8lu\n", g->thresholdCounts[3][0], g->thresholdCounts[3][1], g->thresholdCounts[3][2], g->thresholdCounts[3][3]);
+#define FW uint64FMTW(8)
 
-  if (g->outputHistogram != NULL) {
-    FILE *H = fopen(g->outputHistogram, "w");
+    if (g->FASTQ2 == NULL) {
+      fprintf(stderr, "aClean "FW"\n", g->thresholdCounts[0][0]);
+      fprintf(stderr, "aMurky "FW"\n", g->thresholdCounts[1][0]);
+      fprintf(stderr, "aMatch "FW"\n", g->thresholdCounts[2][0]);
+      fprintf(stderr, "aMixed "FW"\n", g->thresholdCounts[3][0]);
+    } else {
+      fprintf(stderr, "       bClean    bMurky   bMatch     bMixed\n");
+      fprintf(stderr, "aClean "FW"  "FW"  "FW"  "FW"\n", g->thresholdCounts[0][0], g->thresholdCounts[0][1], g->thresholdCounts[0][2], g->thresholdCounts[0][3]);
+      fprintf(stderr, "aMurky "FW"  "FW"  "FW"  "FW"\n", g->thresholdCounts[1][0], g->thresholdCounts[1][1], g->thresholdCounts[1][2], g->thresholdCounts[1][3]);
+      fprintf(stderr, "aMatch "FW"  "FW"  "FW"  "FW"\n", g->thresholdCounts[2][0], g->thresholdCounts[2][1], g->thresholdCounts[2][2], g->thresholdCounts[2][3]);
+      fprintf(stderr, "aMixed "FW"  "FW"  "FW"  "FW"\n", g->thresholdCounts[3][0], g->thresholdCounts[3][1], g->thresholdCounts[3][2], g->thresholdCounts[3][3]);
+    }
+
+#undef FW
 
-    fprintf(H, "# amount of sequence retained\n");
-    for (uint32 i=0; i<1001; i++)
-      if (g->scoreHistogram[i] > 0)
-        fprintf(H, "%.4f\t%u\n", i / 1000.0, g->scoreHistogram[i]);
+    if (g->outputHistogram != NULL) {
+      FILE *H = fopen(g->outputHistogram, "w");
 
-    fclose(H);
+      fprintf(H, "# fraction-masked number-of-sequences\n");
+      for (uint32 i=0; i<1001; i++)
+        if (g->scoreHistogram[i] > 0)
+          fprintf(H, "%.4f\t%u\n", i / 1000.0, g->scoreHistogram[i]);
+
+      fclose(H);
+    }
   }
 
+  delete g->exist;
   delete g;
 
   exit(0);
diff --git a/meryl/mapMers.C b/meryl/mapMers.C
index 98b3edd..f5cf2f0 100644
--- a/meryl/mapMers.C
+++ b/meryl/mapMers.C
@@ -97,20 +97,15 @@ main(int argc, char **argv) {
 
     //  with counts, report mean, mode, median, min, max for each frag.
     if (operation == OP_STATS) {
-      Clen = 0;
-      for (uint32 i=0; i<Cmax; i++)
-        C[i] = 0;
 
-      while (MS->nextMer()) {
-        uint64   cnt = E->count(MS->theFMer()) + E->count(MS->theRMer());
+      Clen = 0;
 
-        if (cnt > 0)
-          C[Clen++] = cnt;
-      }
+      while (MS->nextMer())
+        C[Clen++] = E->count(MS->theFMer()) + E->count(MS->theRMer());
 
-      uint64         mean     = uint64ZERO;
+      uint64         mean     =  uint64ZERO;
       uint64         min      = ~uint64ZERO;
-      uint64         max      = uint64ZERO;
+      uint64         max      =  uint64ZERO;
       uint64         hist[16]  = { 0 };
 
       //  Histogram values are powers of two, e.g., <=1, <=2, <=4, <=8, <=16, <=32, <=64, <=128, <=256, <=512, <=1024, <=4096, <=8192, <=328768
@@ -118,7 +113,7 @@ main(int argc, char **argv) {
       for (uint32 i=0; i<Clen; i++) {
         mean += C[i];
 
-        if ((min > C[i]) && (C[i] > 1))
+        if (min > C[i])
           min = C[i];
         if (max < C[i])
           max = C[i];
@@ -126,7 +121,14 @@ main(int argc, char **argv) {
         hist[ logBaseTwo64(C[i]) ]++;
       }
 
-      mean /= Clen;
+      if (Clen > 0) {
+        mean /= Clen;
+
+      } else {
+        mean = uint64ZERO;
+        min  = uint64ZERO;
+        max  = uint64ZERO;
+      }
 
       fprintf(stdout,
               "%s\t"
diff --git a/meryl/meryl.H b/meryl/meryl.H
index 7527df1..3e8d44e 100644
--- a/meryl/meryl.H
+++ b/meryl/meryl.H
@@ -102,10 +102,15 @@ public:
 
 uint64
 estimateNumMersInMemorySize(uint32 merSize,
-                            uint32 mem,
+                            uint64 mem,
                             bool   positionsEnabled,
                             bool   beVerbose);
 
+uint64
+estimateMemory(uint32 merSize,
+               uint64 numMers,
+               bool   positionsEnabled);
+
 uint32
 optimalNumberOfBuckets(uint32 merSize,
                        uint64 numMers,
diff --git a/sim4dbutils/reportAlignmentDifferences.C b/sim4dbutils/reportAlignmentDifferences.C
index 946ee3c..3940c24 100644
--- a/sim4dbutils/reportAlignmentDifferences.C
+++ b/sim4dbutils/reportAlignmentDifferences.C
@@ -82,7 +82,7 @@ main(int argc, char **argv) {
 
   //  Read matches.
 
-  uint32            lMax = 10240;
+  uint32            lMax = 1048576;
   uint32            lLen = 0;
 
   uint32           *nTot = new uint32 [lMax];

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/kmer-tools.git



More information about the debian-med-commit mailing list