\input cwebmac
\datethis

\N{1}{1}Introduction. This program is a simple filter that sorts and outputs
all
lines of input that do not appear in a given set of sorted files. It is called
{\tt wordtest} because each line of input is considered to be a `word' and
each of the sorted files is considered to be a 'dictionary'. Words are
output when they don't appear in any given dictionary.

The character set and alphabetic order are flexible. Every 8-bit
character is mapped into an integer called its {\it ord}. A character
is called a {\it null\/} if its ord is zero; such characters are
discarded from the input. A character is called a {\it break\/} if
its ord is negative; such characters break the input into so-called words.
Otherwise a character's ord is positive, and the character is called a
{\it letter}. One letter precedes another in alphabetic order if and only
if it has a smaller ord. Two letters are considered identical, for
purposes of sorting, if their ords are the same.

The null character \.{'\\n'} must have ord~0; thus, it must remain null.
Otherwise the ord mapping is arbitrary. If the user doesn't specify
any special mapping, the default ord table simply maps every 8-bit
character code into itself, considering characters to be unsigned char
values in the range 0--255, except that ASCII codes {\tt a-z} are
mapped into the corresponding codes for {\tt A-Z}, and newline is a
break character. Optional command-line arguments, described below, can
change this default mapping to any other desired scheme.

A word is any nonempty sequence of letters that is immediately preceded
and followed by break characters, when nulls are ignored. Technically
speaking, we pretend that a break character is present at the beginning of a
file but not at the end; thus, all letters following the final break character
of a file are ignored, if any such letters are present. Two words are
{\it equivalent\/} to each other if their letters have the same sequence
of ord values. If two or more words of the input are equivalent, only
the first will be output, and it will be output only if it is not
equivalent to any word in the given dictionary files. Words in each
dictionary are assumed to be in lexicographic order and to contain no
nulls. Words in the output file will satisfy these conditions; therefore
{\tt wordtest} can be used to generate and update the dictionaries it needs.
Notice that if no dictionaries are given, {\tt wordtest} will act as a
sorting routine that simply discards nulls and duplicate lines.

\fi

\M{2}The \UNIX/ command line `{\tt wordtest} {\tt [options]} {\tt
[dictionaries]}'
is interpreted by executing option commands from left to right and then by
regarding any remaining arguments as the names of dictionary files.

Most of the option commands are designed to specify the \\{ord} table.
Initially $\\{ord}[\|c]\K\|c$ for each unsigned char code~\|c. The command
$$\line{\hskip5em\tt-b\it string\hfil}$$
makes every character in the string a break character. If the string is
empty, {\tt-b} makes every nonnull character a break (i.e., it sets
$\\{ord}[\|c]\K{-}\T{1}$ for $\T{1}\Z\|c\Z\T{255}$). The command
$$\line{\hskip5em\tt-n\it string\hfil}$$
makes every character in the string a null character. If the string is
empty, {\tt-n} makes every character null. The command
$$\line{\hskip5em\tt-a\it string\hfil}$$
sets the ord of the $k$th element of the string equal to $\delta+k$,
where $\delta$ is an offset value (normally zero). The command
$$\line{\hskip5em\tt-d\it offset\hfil}$$
sets the value of $\delta$; the offset should be a decimal integer between
0 and 255.

There is also an option that has no effect on the \\{ord} table:
$$\line{\hskip5em\tt-m\it length\hfil}$$
defines the length of the longest word. If any word of a file has
more than this many characters, a break is artificially inserted
so that a word of this maximum length is obtained. The default value is 50.
The maximum legal value is 1000.

If the given options do not specify at least one break character,
{\tt wordtest} applies the option commands
$$\vbox{\line{\hskip5em\.{-b"\\}\hfil}
\line{\.{" -d64 -a"abcdefghijklmnopqrstuvwxyz"}\hfil}}$$
which generate the default mapping mentioned above (unless other ords were
changed).

The program is designed to run fastest when there are at most two
dictionary files (usually one large system dictionary and another
personalized one), although it places no limit on the actual number of
dictionaries that can be mentioned on the command line. Users who want
to specify a multitude of dictionaries should ask themselves why they
wouldn't prefer to merge their dictionaries together first (using
{\tt wordtest}).

\Y\B\4\D$\.{MAX\_LENGTH\_DEFAULT}$ \5
\T{50}\par
\B\4\D$\.{MAX\_LENGTH\_LIMIT}$ \5
\T{1000}\par
\fi

\M{3}The general organization of {\tt wordtest} is typical of applications
written in \CEE/, and its approach is quite simple. If any errors are
detected, an indication of the error is sent to the \\{stderr} file and
a nonzero value is returned.

\Y\B\8\#\&{include} \.{<stdio.h>}\7
\X4:Typedefs\X\7
\&{int} ${}\\{main}(\\{argc},\39\\{argv}){}$\1\1\6
\&{int} \\{argc};\C{ the number of command-line arguments }\6
\&{char} ${}{*}\\{argv}[\,]{}$;\C{ the arguments themselves }\2\2\6
${}\{{}$\1\6
\X5:Local variables\X;\6
\X6:Scan the command line arguments\X;\6
\X17:Sort the input into memory\X;\6
\X19:Output all input words that aren't in dictionaries\X;\6
\&{return} \T{0};\6
\4${}\}{}$\2\par
\fi

\M{4}\B\X4:Typedefs\X${}\E{}$\6
\&{typedef} \&{unsigned} \&{char} \&{byte};\C{ our bytes will range from 0 to
255 }\par
\As9\ET20.
\U3.\fi

\M{5}\B\X5:Local variables\X${}\E{}$\6
\&{int} \\{targc};\C{ temporary modifications to \\{argc} }\6
\&{byte} ${}{*}{*}\\{targv}{}$;\C{ pointer to the current argument of interest
}\6
\&{unsigned} \\{delta};\C{ the offset used in the \.{-a} and \.{-d} options }\6
\&{unsigned} \\{max\_length}${}\K\.{MAX\_LENGTH\_DEFAULT}{}$;\C{ longest
allowable word }\6
\&{byte} \\{breakchar};\C{ break character to use in the output }\6
\&{int} \\{ord}[\T{256}];\C{ table of ord values }\6
\&{register} \&{int} \|c;\C{ an all-purpose index }\6
\&{register} \&{byte} ${}{*}\|u,{}$ ${}{*}\|v{}$;\C{ pointer to current string
characters }\par
\As12, 16\ETs22.
\U3.\fi

\M{6}We try to use newline as the output break character, if possible.

\Y\B\4\X6:Scan the command line arguments\X${}\E{}$\6
\&{for} ${}(\|c\K\T{0};{}$ ${}\|c<\T{256};{}$ ${}\|c\PP){}$\1\5
${}\\{ord}[\|c]\K\|c;{}$\2\6
${}\\{delta}\K\T{0};{}$\6
${}\\{targc}\K\\{argc}-\T{1}{}$;\5
${}\\{targv}\K{}$(\&{byte} ${}{*}{*}){}$ \\{argv}${}+\T{1};{}$\6
\&{while} ${}(\\{targc}\W{*}{*}\\{targv}\E\.{'-'}){}$\5
${}\{{}$\1\6
\X7:Execute the option command \\{targv}\X;\6
${}\\{targc}\MM{}$;\5
${}\\{targv}\PP;{}$\6
\4${}\}{}$\2\6
\&{if} ${}(\\{ord}[\.{'\\n'}]<\T{0}){}$\1\5
${}\\{breakchar}\K\.{'\\n'};{}$\2\6
\&{else}\5
${}\{{}$\1\6
${}\\{breakchar}\K\.{'\\0'};{}$\6
\&{for} ${}(\|c\K\T{255};{}$ \|c; ${}\|c\MM){}$\1\6
\&{if} ${}(\\{ord}[\|c]<\T{0}){}$\1\5
${}\\{breakchar}\K\|c;{}$\2\2\6
\&{if} ${}(\R\\{breakchar}){}$\1\5
\X8:Set up the default ords\X;\2\6
\4${}\}{}$\2\6
\X21:Allocate data structures for a total of \\{targc} files\X;\6
\&{for} ( ; \\{targc}; ${}\\{targc}\MM,\39\\{targv}\PP){}$\1\5
\X23:Open the dictionary file named ${*}\\{targv}$\X;\2\par
\U3.\fi

\M{7}\B\X7:Execute the option command \\{targv}\X${}\E{}$\6
\&{switch} ${}(({*}\\{targv})[\T{1}]){}$\5
${}\{{}$\1\6
\4\&{case} \.{'a'}:\6
\&{for} ${}(\|c\K\\{delta},\39\|u\K{*}\\{targv}+\T{2};{}$ ${}{*}\|u;{}$ ${}\|u%
\PP){}$\1\5
${}\\{ord}[{*}\|u]\K\PP\|c{}$;\5
\2\&{break};\6
\4\&{case} \.{'b'}:\6
\&{if} ${}(({*}\\{targv})[\T{2}]){}$\1\6
\&{for} ${}(\|u\K{*}\\{targv}+\T{2};{}$ ${}{*}\|u;{}$ ${}\|u\PP){}$\1\5
${}\\{ord}[{*}\|u]\K{-}\T{1};{}$\2\2\6
\&{else}\1\6
\&{for} ${}(\|c\K\T{1};{}$ ${}\|c<\T{256};{}$ ${}\|c\PP){}$\1\5
${}\\{ord}[\|c]\K{-}\T{1};{}$\2\2\6
\&{break};\6
\4\&{case} \.{'n'}:\6
\&{if} ${}(({*}\\{targv})[\T{2}]){}$\1\6
\&{for} ${}(\|u\K{*}\\{targv}+\T{2};{}$ ${}{*}\|u;{}$ ${}\|u\PP){}$\1\5
${}\\{ord}[{*}\|u]\K\T{0};{}$\2\2\6
\&{else}\1\6
\&{for} ${}(\|c\K\T{1};{}$ ${}\|c<\T{256};{}$ ${}\|c\PP){}$\1\5
${}\\{ord}[\|c]\K\T{0};{}$\2\2\6
\&{break};\6
\4\&{case} \.{'d'}:\6
\&{if} (\\{sscanf}((\&{char} ${}{*}){}$ ${}{*}\\{targv}+\T{2},\39\.{"\%u"},\39{%
\AND}\\{delta})\E\T{1}\W\\{delta}<\T{256}){}$\1\5
\&{break};\2\6
\&{goto} \\{print\_usage};\6
\4\&{case} \.{'m'}:\6
\&{if} (\\{sscanf}((\&{char} ${}{*}){}$ ${}{*}\\{targv}+\T{2},\39\.{"\%u"},\39{%
\AND}\\{max\_length})\E\T{1}\AND\\{max\_length}\Z\.{MAX\_LENGTH\_LIMIT}){}$\1\5
\&{break};\2\6
\&{goto} \\{print\_usage};\6
\4\&{default}:\5
\\{print\_usage}:\5
${}\\{fprintf}(\\{stderr},\39\.{"Usage:\ \%s\ \{-\{\{a|b|n}\)\.{\}string|\{d|m%
\}number\}}\)\.{\}*\ dictionaryname*\\n}\)\.{"},\39{*}\\{argv});{}$\6
\&{return} ${}{-}\T{1};{}$\6
\4${}\}{}$\2\par
\U6.\fi

\M{8}\B\X8:Set up the default ords\X${}\E{}$\6
${}\{{}$\1\6
${}\\{ord}[\.{'\\n'}]\K{-}\T{1}{}$;\C{ newline is break character }\6
${}\\{breakchar}\K\.{'\\n'};{}$\6
\&{for} ${}(\|c\K\T{1};{}$ ${}\|c\Z\T{26};{}$ ${}\|c\PP){}$\1\5
${}\\{ord}[\.{'a'}-\T{1}+\|c]\K\.{'A'}-\T{1}+\|c;{}$\2\6
\4${}\}{}$\2\par
\U6.\fi

\N{1}{9}Treaps. The most interesting part of this program is its sorting
algorithm,
which is based on the ``treap'' data structure of Aragon and Seidel
[{\sl 30th IEEE Symposium on Foundations of Computer Science\/} (1989),
540--546].
A treap is a binary tree whose nodes have two key fields. The primary
key, which in our application is a word from the input, obeys
tree-search order: All descendants of the left child of node~$p$ have
a primary key that is less than the primary key of~$p$, and all descendants
of its right child have a primary key that is greater. The secondary key,
which in our application is a unique pseudorandom integer attached to
each input word, obeys heap order: The secondary key of~$p$'s children
is greater than $p$'s own secondary key.

A given set of nodes with distinct primary keys and distinct secondary
keys can be made into a treap in exactly one way. This unique treap
can be obtained, for example, by using ordinary tree insertion with
respect to primary keys while inserting nodes in order of their
secondary keys. It follows that, if the secondary keys are random,
the binary tree will almost always be quite well balanced.

We will compute secondary keys as unsigned long integers, assigning
the key $(cn)\bmod 2^{32}$ to the $n$th node, where $c$ is an odd
number. This will guarantee that the secondary keys are distinct.
By choosing $c$ close to $2^{32}/\phi$, where $\phi$ is the golden
ratio $(1+\sqrt5\,)/2$, we also spread the values out in a fashion that
is unlikely to match any existing order in the data.

\Y\B\4\D$\.{PHICLONE}$ \5
\T{2654435769}\C{ $\approx 2^{32}/\phi$ }\par
\Y\B\4\X4:Typedefs\X${}\mathrel+\E{}$\6
\&{typedef} \&{struct} \&{node\_struct} ${}\{{}$\1\6
\&{struct} \&{node\_struct} ${}{*}\\{left},{}$ ${}{*}\\{right}{}$;\C{ children
}\6
\&{byte} ${}{*}\\{keyword}{}$;\C{ primary key }\6
\&{unsigned} \&{long} \\{rank};\C{ secondary key }\2\6
${}\}{}$ \&{node};\C{ node of a treap }\par
\fi

\M{10}We want to be able to compare two strings rapidly with respect to
lexicographic order, as defined by the \\{ord} table. This can be done
if one string is delimited by \.{'\\0'} as usual, while the other is
delimited by a break character. Then we are sure to have an unequal
comparison, and the inner loop is fast.

Here is a routine that checks to see if a word is already present in the
treap.  The word is assumed to be in \\{buffer}, terminated by \\{breakchar}.
The words in the treap are terminated by nulls. The
treap is accessed by means of \\{root}, a pointer to its root node.

\Y\B\4\X10:Search for \\{buffer} in the treap; \&{goto} \\{found} if it's there%
\X${}\E{}$\6
${}\{{}$\5
\1\&{register} \&{node} ${}{*}\|p\K\\{root};{}$\7
\&{while} (\|p)\5
${}\{{}$\1\6
\&{for} ${}(\|u\K\\{buffer},\39\|v\K\|p\MG\\{keyword};{}$ ${}\\{ord}[{*}\|u]\E%
\\{ord}[{*}\|v];{}$ ${}\|u\PP,\39\|v\PP){}$\1\5
;\2\6
\&{if} ${}({*}\|v\E\.{'\\0'}\W{*}\|u\E\\{breakchar}){}$\1\5
\&{goto} \\{found};\2\6
\&{if} ${}(\\{ord}[{*}\|u]<\\{ord}[{*}\|v]){}$\1\5
${}\|p\K\|p\MG\\{left};{}$\2\6
\&{else}\1\5
${}\|p\K\|p\MG\\{right};{}$\2\6
\4${}\}{}$\2\6
\4${}\}{}$\2\par
\U17.\fi

\M{11}We don't need to insert nodes into the treap as often as we need to
look words up, so we don't mind repeating the comparisons already made
when we discover that insertion is necessary. (Actually a more comprehensive
study of this tradeoff ought to be done. But not today; I am trying
here to keep the program short and sweet.)

The insertion algorithm proceeds just as the lookup algorithm until
we come to a node whose rank is larger than the rank of the node
to be inserted. We insert the new node in its place, then split the
old node and its descendants into two subtrees that will become the
left and right subtrees of the new node.

\Y\B\4\X11:Insert the \\{buffer} word into the treap\X${}\E{}$\6
${}\{{}$\5
\1\&{register} \&{node} ${}{*}\|p,{}$ ${}{*}{*}\|q,{}$ ${}{*}{*}\\{qq},{}$
${}{*}\|r;{}$\7
${}\\{current\_rank}\MRL{+{\K}}\.{PHICLONE}{}$;\C{ unsigned addition mod
$2^{32}$ }\6
${}\|p\K\\{root}{}$;\5
${}\|q\K{\AND}\\{root};{}$\6
\&{while} (\|p)\5
${}\{{}$\1\6
\&{if} ${}(\|p\MG\\{rank}>\\{current\_rank}){}$\1\5
\&{break};\C{ end of the first phase }\2\6
\&{for} ${}(\|u\K\\{buffer},\39\|v\K\|p\MG\\{keyword};{}$ ${}\\{ord}[{*}\|u]\E%
\\{ord}[{*}\|v];{}$ ${}\|u\PP,\39\|v\PP){}$\1\5
;\2\6
\&{if} ${}(\\{ord}[{*}\|u]<\\{ord}[{*}\|v]){}$\1\5
${}\|q\K{\AND}(\|p\MG\\{left}),\39\|p\K{*}\|q;{}$\2\6
\&{else}\1\5
${}\|q\K{\AND}(\|p\MG\\{right}),\39\|p\K{*}\|q;{}$\2\6
\4${}\}{}$\2\6
\X14:Set \|r to the address of a new node, and move \\{buffer} into it\X;\6
${}\|r\MG\\{rank}\K\\{current\_rank};{}$\6
${}{*}\|q\K\|r{}$;\C{ link the new node into the tree }\6
\X13:Split subtree \|p and attach it below node \|r\X;\6
\4${}\}{}$\2\par
\U17.\fi

\M{12}\B\X5:Local variables\X${}\mathrel+\E{}$\6
\&{unsigned} \&{long} \\{current\_rank}${}\K\T{0}{}$;\C{ pseudorandom number }%
\par
\fi

\M{13}At this point \|p may already be empty. If not, we can hook its
parts together easily.  (A formal proof is a bit tricky, but the computer
doesn't slow down like people do when they get to a conceptually harder
part of an algorithm.)

\Y\B\4\X13:Split subtree \|p and attach it below node \|r\X${}\E{}$\6
$\|q\K{\AND}(\|r\MG\\{left}){}$;\5
${}\\{qq}\K{\AND}(\|r\MG\\{right}){}$;\C{ slots to fill in as we split the
subtree }\6
\&{while} (\|p)\5
${}\{{}$\1\6
\&{for} ${}(\|u\K\\{buffer},\39\|v\K\|p\MG\\{keyword};{}$ ${}\\{ord}[{*}\|u]\E%
\\{ord}[{*}\|v];{}$ ${}\|u\PP,\39\|v\PP){}$\1\5
;\2\6
\&{if} ${}(\\{ord}[{*}\|u]<\\{ord}[{*}\|v]){}$\5
${}\{{}$\1\6
${}{*}\\{qq}\K\|p;{}$\6
${}\\{qq}\K{\AND}(\|p\MG\\{left});{}$\6
${}\|p\K{*}\\{qq};{}$\6
\4${}\}{}$\2\6
\&{else}\5
${}\{{}$\1\6
${}{*}\|q\K\|p;{}$\6
${}\|q\K{\AND}(\|p\MG\\{right});{}$\6
${}\|p\K{*}\|q;{}$\6
\4${}\}{}$\2\6
\4${}\}{}$\2\6
${}{*}\|q\K{*}\\{qq}\K\NULL{}$;\par
\U11.\fi

\M{14}We allocate node memory dynamically, in blocks of 100 nodes at a time.
We also allocate string memory dynamically, 1000 characters at once
(in addition to space for the current string).
The variable \|l will be set to the length of the word in \\{buffer}.

\Y\B\4\D$\.{NODES\_PER\_BLOCK}$ \5
\T{100}\par
\B\4\D$\.{CHARS\_PER\_BLOCK}$ \5
\T{1000}\par
\B\4\D$\\{out\_of\_mem}(\|x)$ \6
${}\{{}$\5
\1${}\\{fprintf}(\\{stderr},\39\.{"\%s:\ Memory\ exhauste}\)\.{d!\\n"},\39{*}%
\\{argv});{}$\6
\&{return} \|x;\5
${}\}{}$\2\par
\Y\B\4\X14:Set \|r to the address of a new node, and move \\{buffer} into it%
\X${}\E{}$\6
\&{if} ${}(\\{next\_node}\E\\{bad\_node}){}$\5
${}\{{}$\1\6
${}\\{next\_node}\K{}$(\&{node} ${}{*}){}$ \\{calloc}${}(\.{NODES\_PER\_BLOCK},%
\39\&{sizeof}(\&{node}));{}$\6
\&{if} ${}(\\{next\_node}\E\NULL){}$\1\5
${}\\{out\_of\_mem}({-}\T{2});{}$\2\6
${}\\{bad\_node}\K\\{next\_node}+\.{NODES\_PER\_BLOCK};{}$\6
\4${}\}{}$\2\6
${}\|r\K\\{next\_node}\PP;{}$\6
\X15:Move \\{buffer} to a new place in the string memory, and make $\|r\MG%
\\{keyword}$ point to it\X;\par
\U11.\fi

\M{15}\B\X15:Move \\{buffer} to a new place in the string memory, and make $\|r%
\MG\\{keyword}$ point to it\X${}\E{}$\6
\&{if} ${}(\\{next\_string}+\|l+\T{1}\G\\{bad\_string}){}$\5
${}\{{}$\5
\1\&{int} \\{block\_size}${}\K\.{CHARS\_PER\_BLOCK}+\|l+\T{1};{}$\7
${}\\{next\_string}\K{}$(\&{byte} ${}{*}){}$ \\{malloc}(\\{block\_size});\6
\&{if} ${}(\\{next\_string}\E\NULL){}$\1\5
${}\\{out\_of\_mem}({-}\T{3});{}$\2\6
${}\\{bad\_string}\K\\{next\_string}+\\{block\_size};{}$\6
\4${}\}{}$\2\6
${}\|r\MG\\{keyword}\K\\{next\_string};{}$\6
\&{for} ${}(\|u\K\\{buffer},\39\|v\K\\{next\_string};{}$ ${}\\{ord}[{*}\|u]>%
\T{0};{}$ ${}\|u\PP,\39\|v\PP){}$\1\5
${}{*}\|v\K{*}\|u;{}$\2\6
${}{*}\|v\K\.{'\\0'};{}$\6
${}\\{next\_string}\K\|v+\T{1}{}$;\par
\U14.\fi

\M{16}We had better define the variables we've been assuming in these
storage allocation routines.

\Y\B\4\X5:Local variables\X${}\mathrel+\E{}$\6
\&{node} ${}{*}\\{next\_node}\K\NULL,{}$ ${}{*}\\{bad\_node}\K\NULL;{}$\6
\&{byte} ${}{*}\\{next\_string}\K\NULL,{}$ ${}{*}\\{bad\_string}\K\NULL;{}$\6
\&{node} ${}{*}\\{root}\K\NULL;{}$\6
\&{byte} ${}{*}\\{buffer};{}$\6
\&{int} \|l;\C{ length of current string in \\{buffer} }\par
\fi

\M{17}The mechanisms for sorting the input words are now all in place.
We merely need to invoke them at the right times.

\Y\B\4\X17:Sort the input into memory\X${}\E{}$\6
$\\{buffer}\K{}$(\&{byte} ${}{*}){}$ \\{malloc}${}(\\{max\_length}+\T{1});{}$\6
\&{if} ${}(\\{buffer}\E\NULL){}$\1\5
${}\\{out\_of\_mem}({-}\T{5});{}$\2\6
\&{while} (\T{1})\5
${}\{{}$\1\6
\X18:Set \\{buffer} to the next word from \\{stdin}; \&{goto} \\{done} if file
ends\X;\6
\&{if} (\|l)\5
${}\{{}$\1\6
\X10:Search for \\{buffer} in the treap; \&{goto} \\{found} if it's there\X;\6
\X11:Insert the \\{buffer} word into the treap\X;\6
\4\\{found}:\5
;\6
\4${}\}{}$\2\6
\4${}\}{}$\2\6
\4\\{done}:\5
;\par
\U3.\fi

\M{18}\B\X18:Set \\{buffer} to the next word from \\{stdin}; \&{goto} \\{done}
if file ends\X${}\E{}$\6
$\|u\K\\{buffer}{}$;\5
${}\|l\K\T{0};{}$\6
\&{while} ${}(\|l<\\{max\_length}){}$\5
${}\{{}$\1\6
${}\|c\K\\{getchar}(\,);{}$\6
\&{if} ${}(\|c\E\.{EOF}){}$\5
${}\{{}$\1\6
\&{if} (\\{ferror}(\\{stdin}))\5
${}\{{}$\1\6
${}\\{fprintf}(\\{stderr},\39\.{"\%s:\ File\ read\ error}\)\.{\ on\ standard\
input!\\}\)\.{n"},\39{*}\\{argv});{}$\6
\&{return} ${}{-}\T{6};{}$\6
\4${}\}{}$\2\6
\&{goto} \\{done};\C{ end of file; the current word, if any, is discarded }\6
\4${}\}{}$\2\6
\&{if} ${}(\\{ord}[\|c]\Z\T{0}){}$\5
${}\{{}$\1\6
\&{if} ${}(\\{ord}[\|c]<\T{0}){}$\1\5
\&{break};\2\6
\4${}\}{}$\2\6
\&{else}\5
${}\{{}$\1\6
${}{*}\|u\PP\K{}$(\&{byte}) \|c;\6
${}\|l\PP;{}$\6
\4${}\}{}$\2\6
\4${}\}{}$\2\6
${}{*}\|u\K\\{breakchar}{}$;\par
\U17.\fi

\M{19}At the end we want to traverse the treap in symmetric order, so that
we see its words in alphabetic order. We might as well destroy the
treap structure as we do this. During this phase, \\{root} will point
to a stack of nodes that remain to be visited (followed by traversal
of their right subtrees).

\Y\B\4\X19:Output all input words that aren't in dictionaries\X${}\E{}$\6
\&{if} ${}(\\{root}\I\NULL){}$\5
${}\{{}$\5
\1\&{register} \&{node} ${}{*}\|p,{}$ ${}{*}\|q;{}$\7
${}\|p\K\\{root};{}$\6
${}\\{root}\K\NULL;{}$\6
\&{while} (\T{1})\5
${}\{{}$\1\6
\&{while} ${}(\|p\MG\\{left}\I\NULL){}$\5
${}\{{}$\1\6
${}\|q\K\|p\MG\\{left};{}$\6
${}\|p\MG\\{left}\K\\{root}{}$;\C{ \\{left} links are now used for the stack }\6
${}\\{root}\K\|p;{}$\6
${}\|p\K\|q;{}$\6
\4${}\}{}$\2\6
\4\\{visit}:\5
\X25:Output $\|p\MG\\{keyword}$, if it's not in the dictionaries\X;\6
\&{if} ${}(\|p\MG\\{right}\E\NULL){}$\5
${}\{{}$\1\6
\&{if} ${}(\\{root}\E\NULL){}$\1\5
\&{break};\C{ the stack is empty, we're done }\2\6
${}\|p\K\\{root};{}$\6
${}\\{root}\K\\{root}\MG\\{left}{}$;\C{ pop the stack }\6
\&{goto} \\{visit};\6
\4${}\}{}$\2\6
\&{else}\1\5
${}\|p\K\|p\MG\\{right};{}$\2\6
\4${}\}{}$\2\6
\4${}\}{}$\2\par
\U3.\fi

\N{1}{20}The dictionaries. So now all we have to do is provide a mechanism
for reading the words in the dictionaries. The dictionaries are sorted,
and by now the input words have been sorted too.
So we need only scan through the
dictionaries once; we'll try to zoom through as quickly as possible.

First we need data structures. There will be an array of pointers to filenodes,
for all dictionary files currently open. Each filenode will contain
a buffer of size $\.{BUFSIZ}+\T{1}$ for raw input bytes not yet scanned,
as well as a buffer of size $\.{MAX\_LENGTH\_LIMIT}+\T{1}$ for the current word
being considered.

\Y\B\4\X4:Typedefs\X${}\mathrel+\E{}$\6
\&{typedef} \&{struct} \&{filenode\_struct} ${}\{{}$\1\6
\&{struct} \&{filenode\_struct} ${}{*}\\{link}{}$;\C{ pointer to next open file
}\6
\&{FILE} ${}{*}\\{dfile}{}$;\C{ dictionary file }\6
\&{byte} ${}\\{buf}[\.{BUFSIZ}+\T{1}],{}$ ${}\\{curword}[\.{MAX\_LENGTH%
\_LIMIT}+\T{1}];{}$\6
\&{byte} ${}{*}\\{pos}{}$;\C{ current position in \\{buf} }\6
\&{byte} ${}{*}\\{limit}{}$;\C{ end of input bytes in \\{buf} }\6
\&{byte} ${}{*}\\{endword}{}$;\C{ the first break character in \\{curword} }\2\6
${}\}{}$ \&{filenode};\par
\fi

\M{21}\B\X21:Allocate data structures for a total of \\{targc} files\X${}\E{}$\6
\&{if} (\\{targc})\5
${}\{{}$\1\6
${}\\{curfile}\K{}$(\&{filenode} ${}{*}){}$ \\{calloc}${}(\\{targc},\39%
\&{sizeof}(\&{filenode}));{}$\6
\&{if} ${}(\\{curfile}\E\NULL){}$\1\5
${}\\{out\_of\_mem}({-}\T{7});{}$\2\6
\&{for} ${}(\|f\K\\{curfile};{}$ ${}\|f<\\{curfile}+\\{targc}-\T{1};{}$ ${}\|f%
\PP){}$\1\5
${}\|f\MG\\{link}\K\|f+\T{1};{}$\2\6
${}\|f\MG\\{link}\K\\{curfile}{}$;\C{ circular linking }\6
\4${}\}{}$\2\6
\&{else}\1\5
${}\\{curfile}\K\NULL{}$;\2\par
\U6.\fi

\M{22}\B\X5:Local variables\X${}\mathrel+\E{}$\6
\&{filenode} ${}{*}\\{curfile}{}$;\C{ current filenode of interest }\6
\&{filenode} ${}{*}\|f{}$;\C{ temporary register for filenode list processing }%
\par
\fi

\M{23}\B\X23:Open the dictionary file named ${*}\\{targv}$\X${}\E{}$\6
${}\{{}$\1\6
${}\\{curfile}\MG\\{dfile}\K\\{fopen}{}$((\&{char} ${}{*}){}$ ${}{*}\\{targv},%
\39\.{"r"});{}$\6
\&{if} ${}(\\{curfile}\MG\\{dfile}\E\NULL){}$\5
${}\{{}$\1\6
${}\\{fprintf}(\\{stderr},\39\.{"\%s:\ Can't\ open\ dict}\)\.{ionary\ file\ %
\%s!\\n"},\39{*}\\{argv},\39{}$(\&{char} ${}{*}){}$ ${}{*}\\{targv});{}$\6
\&{return} ${}{-}\T{8};{}$\6
\4${}\}{}$\2\6
${}\\{curfile}\MG\\{pos}\K\\{curfile}\MG\\{limit}\K\\{curfile}\MG\\{buf}{}$;\C{
\\{buf} is empty }\6
${}\\{curfile}\MG\\{buf}[\T{0}]\K\.{'\\0'};{}$\6
${}\\{curfile}\MG\\{endword}\K\\{curfile}\MG\\{curword}{}$;\C{ \\{curword} is
empty too }\6
${}\\{curfile}\MG\\{curword}[\T{0}]\K\\{breakchar};{}$\6
${}\\{curfile}\K\\{curfile}\MG\\{link}{}$;\C{ move to next filenode }\6
\4${}\}{}$\2\par
\U6.\fi

\M{24}We will implicitly merge the dictionaries together by using a brute force
scheme that works fine when there are only a few of them. Namely,
\\{curfile} will point to a file having the currently smallest
current word. To get to the next word of the merge, we advance to the
next word in that file, comparing it with the current words of the
other files to see if \\{curfile} should switch to one of them.
When we get to the end of a file, its filenode simply leaves the circular
list. Eventually the list will be empty, and we will set \\{curfile} to
$\NULL$; we will then have seen all the dictionary words in order.

\fi

\M{25}\B\X25:Output $\|p\MG\\{keyword}$, if it's not in the dictionaries\X${}%
\E{}$\6
\&{while} ${}(\\{curfile}\I\NULL){}$\5
${}\{{}$\1\6
\&{for} ${}(\|u\K\|p\MG\\{keyword},\39\|v\K\\{curfile}\MG\\{curword};{}$ ${}%
\\{ord}[{*}\|u]\E\\{ord}[{*}\|v];{}$ ${}\|u\PP,\39\|v\PP){}$\1\5
;\2\6
\&{if} ${}({*}\|u\E\.{'\\0'}\W{*}\|v\E\\{breakchar}){}$\1\5
\&{goto} \\{word\_done};\C{ we found it in the dictionary }\2\6
\&{if} ${}(\\{ord}[{*}\|u]<\\{ord}[{*}\|v]){}$\1\5
\&{break};\C{ we didn't find it }\2\6
\X27:Advance to the next dictionary word\X;\6
\4${}\}{}$\2\6
\X26:Print $\|p\MG\\{keyword}$ and \\{breakchar} on \\{stdout}\X\6
\4\\{word\_done}:\5
;\par
\U19.\fi

\M{26}\B\X26:Print $\|p\MG\\{keyword}$ and \\{breakchar} on \\{stdout}\X${}%
\E{}$\6
\&{for} ${}(\|u\K\|p\MG\\{keyword};{}$ ${}{*}\|u;{}$ ${}\|u\PP){}$\1\5
${}\\{putchar}({*}\|u);{}$\2\6
\\{putchar}(\\{breakchar});\par
\U25.\fi

\M{27}\B\X27:Advance to the next dictionary word\X${}\E{}$\6
\X28:Read a new word into $\\{curfile}\MG\\{curword}$, as fast as you can\X;\6
\X30:Adjust \\{curfile}, if necessary, to point to a file with minimal %
\\{curword}\X;\par
\U25.\fi

\M{28}The dictionaries are supposed to be in order, and they shouldn't
contain nulls. But if they fail to meet these criteria, we don't want
{\tt wordtest} to crash; it should just run more slowly and/or more
peculiarly.

The logic of the code here removes null characters, at the cost of speed.
If the dictionary contains words out of order, say $\alpha>\beta$ where
$\alpha$ precedes $\beta$ in the file, the effect will be as if $\beta$
were not present. (In particular, if the dictionary would happen to have a null
word because of a break character inserted by our \\{max\_length} logic,
that null word would cause no harm, because a null word is always less than
any nonnull word.)

A null character always appears in $\\{curfile}\MG\\{limit}$.

\Y\B\4\X28:Read a new word into $\\{curfile}\MG\\{curword}$, as fast as you can%
\X${}\E{}$\6
$\|v\K\\{curfile}\MG\\{curword};{}$\6
${}\|l\K\\{max\_length}{}$;\C{ here \|l represents max characters to put in %
\\{curword} }\6
\&{while} (\T{1})\5
${}\{{}$\5
\1\&{register} \&{byte} ${}{*}\|w\K\\{curfile}\MG\\{limit};{}$\7
${}\|u\K\\{curfile}\MG\\{pos};{}$\6
\&{if} ${}(\|u+\|l\G\|w){}$\1\6
\&{while} ${}(\\{ord}[{*}\|u]>\T{0}){}$\1\5
${}{*}\|v\PP\K{*}\|u\PP{}$;\C{ this is the inner loop }\2\2\6
\&{else}\5
${}\{{}$\1\6
${}\|w\K\|u+\|l;{}$\6
${}\|c\K{*}\|w;{}$\6
${}{*}\|w\K\.{'\\0'}{}$;\C{ temporarily store a null to avoid overlong string }%
\6
\&{while} ${}(\\{ord}[{*}\|u]>\T{0}){}$\1\5
${}{*}\|v\PP\K{*}\|u\PP{}$;\C{ this too is the inner loop }\2\6
${}{*}\|w\K\|c{}$;\C{ restore the damaged byte }\6
\4${}\}{}$\2\6
\&{if} ${}(\\{ord}[{*}\|u]<\T{0}){}$\5
${}\{{}$\1\6
${}\\{curfile}\MG\\{pos}\K\|u+\T{1}{}$;\C{ good, we found the next break
character }\6
\&{break};\6
\4${}\}{}$\2\6
${}\|l\MRL{-{\K}}\|u-\\{curfile}\MG\\{pos};{}$\6
\&{if} ${}(\|l\E\T{0}){}$\5
${}\{{}$\C{ \\{max\_length} reached }\1\6
${}\\{curfile}\MG\\{pos}\K\|u;{}$\6
\&{break};\6
\4${}\}{}$\2\6
\&{if} ${}(\|u\E\|w){}$\5
${}\{{}$\C{ we're at $\\{curfile}\MG\\{limit}$ }\1\6
\X29:Refill $\\{curfile}\MG\\{buf}$; or remove the current file from the
circular list and \&{goto} \\{update\_done}, if it has ended\X;\6
\4${}\}{}$\2\6
\&{else}\1\5
${}\\{curfile}\MG\\{pos}\K\|u+\T{1}{}$;\C{ bypass a null character in the
dictionary }\2\6
\4${}\}{}$\2\6
${}\\{curfile}\MG\\{endword}\K\|v;{}$\6
${}{*}\|v\K\\{breakchar};{}$\6
\4\\{update\_done}:\5
;\par
\U27.\fi

\M{29}\B\X29:Refill $\\{curfile}\MG\\{buf}$; or remove the current file from
the circular list and \&{goto} \\{update\_done}, if it has ended\X${}\E{}$\6
\&{if} ${}(\\{ferror}(\\{curfile}\MG\\{dfile})){}$\5
${}\{{}$\1\6
${}\\{fprintf}(\\{stderr},\39\.{"\%s:\ File\ read\ error}\)\.{\ on\ dictionary\
file!}\)\.{\\n"},\39{*}\\{argv});{}$\6
\&{return} ${}{-}\T{9};{}$\6
\4${}\}{}$\2\6
\&{if} ${}(\\{feof}(\\{curfile}\MG\\{dfile})){}$\5
${}\{{}$\1\6
${}\|f\K\\{curfile}\MG\\{link};{}$\6
\&{if} ${}(\|f\E\\{curfile}){}$\1\5
${}\\{curfile}\K\NULL{}$;\C{ the last dictionary file has ended }\2\6
\&{else}\5
${}\{{}$\1\6
\&{while} ${}(\|f\MG\\{link}\I\\{curfile}){}$\1\5
${}\|f\K\|f\MG\\{link};{}$\2\6
${}\|f\MG\\{link}\K\\{curfile}\MG\\{link}{}$;\C{ remove a filenode from the
circular list }\6
${}\\{curfile}\K\|f{}$;\C{ and point to one of the remaining filenodes }\6
\4${}\}{}$\2\6
\&{goto} \\{update\_done};\6
\4${}\}{}$\2\6
${}\\{curfile}\MG\\{limit}\K\\{curfile}\MG\\{buf}+\\{fread}(\\{curfile}\MG%
\\{buf},\39\T{1},\39\.{BUFSIZ},\39\\{curfile}\MG\\{dfile});{}$\6
${}{*}\\{curfile}\MG\\{limit}\K\.{'\\0'};{}$\6
${}\\{curfile}\MG\\{pos}\K\\{curfile}\MG\\{buf}{}$;\par
\U28.\fi

\M{30}\B\X30:Adjust \\{curfile}, if necessary, to point to a file with minimal %
\\{curword}\X${}\E{}$\6
\&{if} ${}(\\{curfile}\I\NULL){}$\5
${}\{{}$\5
\1\&{filenode} ${}{*}\\{sentinel}\K\\{curfile};{}$\7
\&{for} ${}(\|f\K\\{curfile}\MG\\{link};{}$ ${}\|f\I\\{sentinel};{}$ ${}\|f\K%
\|f\MG\\{link}){}$\1\5
\X31:Change \\{curfile} to \|f if $\|f\MG\\{curword}<\\{curfile}\MG\\{curword}$%
\X;\2\6
\4${}\}{}$\2\par
\U27.\fi

\M{31}\B\X31:Change \\{curfile} to \|f if $\|f\MG\\{curword}<\\{curfile}\MG%
\\{curword}$\X${}\E{}$\6
${}\{{}$\1\6
${}{*}\|f\MG\\{endword}\K\.{'\\0'};{}$\6
\&{for} ${}(\|u\K\|f\MG\\{curword},\39\|v\K\\{curfile}\MG\\{curword};{}$ ${}%
\\{ord}[{*}\|u]\E\\{ord}[{*}\|v];{}$ ${}\|u\PP,\39\|v\PP){}$\1\5
;\2\6
\&{if} ${}(\\{ord}[{*}\|u]<\\{ord}[{*}\|v]){}$\1\5
${}\\{curfile}\K\|f;{}$\2\6
${}{*}\|f\MG\\{endword}\K\\{breakchar};{}$\6
\4${}\}{}$\2\par
\U30.\fi

\N{1}{32}Index. Here is a list of the identifiers used by {\tt wordtest},
showing the sections in which they appear, underlined at points
of definition.
\fi


\inx
\fin
\con
