% % find.nw % Find patterns in text files % % Kiyoshi Akima % 2004.06.28 % 2004.07.13 % % to weave: noweave -delay -index find.nw >find.tex % to tangle: nowtangle find.nw >find.b % \addtocounter{secnumdepth}{-1}% \def\nohead{\medskip\noindent}% \def\bcpl/{\textsf{BCPL}}% \newcommand{\longpage}{\enlargethispage{\baselineskip}}% \documentclass[twoside]{article}\usepackage{noweb,multicol}% \pagestyle{noweb}\noweboptions{}\begin{document}% @ \section{Find} [[find]]\hskip.5in[[FROM/A,TO/K,PAT/K,N/S]] The [[find]] command copies the file given by the [[FROM]] argument to the file given by the [[TO]] argument. In the process it scans the text for occurences of a search pattern. Only lines containing or not containing the search pattern are output; that is, [[find]] selects from a file just those lines containing a search pattern or just those lines not containing it. The search pattern is given by the [[PAT]] argument and is formed according to the rules given below. If the pattern is prefixed by a tilde symbol ([[~]]), then lines not containing the pattern are output, otherwise lines containing the pattern are output. If the [[N]] switch is given then the lines are numbered on output. @ \subsection{Patterns} The simplest form of a search pattern is a character string identical to the one sought. Preceding the pattern with a grave accent ([[`]]) specifies that the string must appear at the beginning of a line. The grave accent in any other position has no special meaning. Terminating a pattern with an apostrophe ([[']]) specifies that the string must occur at the end of a line. An apostrophe in any other position has no special meaning. The following patterns illustrate their use:\par\noindent \begin{tabular}{ll} \textbf{pattern}&\textbf{meaning}\\[3pt] [[`abcd]]&the string ``abcd'' at the beginning of a line\\ [[xyz']]&the string ``xyz'' at the end of a line\\ [[`xxx']]&a line consisting only of ``xxx''\\ [[ab'cd`e]]&the string ``ab'cd`e'' occuring anywhere in a line\\ \end{tabular}\par A question mark ([[?]]) in a pattern matches any character in that position of a string. Thus the pattern [[f??t]] matches ``foot'', ``feet'', ``f it'', and so on. An asterisk ([[*]]) causes a match on zero or more occurences of the preceding character. An asterisk at the beginning of a pattern has no special meaning. The following patterns illustrate the use of the asterisk:\par\noindent \begin{tabular}{ll} \textbf{pattern}&\textbf{matching strings}\\[3pt] [[*abc]]&``*abc''\\ [[a*bc]]&``bc'', ``abc'', ``aabc'', ``aaabc'', \dots\\ [[aa*bc]]&``abc'', ``aabc'', ``aaabc'', ``aaaabc'', \dots\\ [[s?*p]]&``sp'', ``sxp'', ``sleep'', ``s12 xp'', \dots\\ \end{tabular}\par @ \subsection{Escape sequences} Sometimes it is necessary to enter nonprintable characters or characters that ordinarily have special meaning. You can enter such characters from the keyboard by using the colon as an escape character. An escape character changes the meaning of the character that follows it. Together the escape character and the character following it are seen as a single character by the command. The escape sequences are:\par\noindent \begin{tabular}{ll} [[:b]]&backspace\\ [[:n]]&newline\\ [[:s]]&space\\ [[:t]]&tab\\ [[]]&the actual character given\\ \end{tabular}\par Some special characters known as \textit{metacharacters} have special meaning when they appear in the pattern. You may use the [[]] escape sequence to force them to be seen as themselves in these contexts. The colon, having a special use (escape character), must be escaped to be accepted as itself; thus [[::]] is taken for a single colon. Provision is made for the space character, since if an actual space were included in the pattern it would delimit the pattern. @ \subsection{Metacharacters}\label{metacharactersdoc}\longpage Certain characters assume special meanings when they appearn in the pattern. As a group these characters are designated \textit{metacharacters} (as opposed to ordinary characters). Since these metacharacters occasionally need to appear as ordinary characters in a search pattern, they may, in such cases, be entered as escape sequences. The metacharacters are given below:\par\noindent \begin{tabular}{lll} \textit{symbol}&\textit{name}&\textit{use}\\[3pt] [[:]]&colon&escape character\\[1pt] [[`]]&grave accent&matches the beginning of the line\\[1pt] [[']]&apostrophe&matches the end of the line\\[1pt] [[?]]&question mark&matches any character\\[1pt] [[*]]&asterisk&matches zero or more occurences of the\\[-2pt]&&preceding character\\[1pt] [[[]]&left bracket&introduces a character class definition\\[1pt] [[]]]&right bracket&terminates a character class definition\\[1pt] [[-]]&hyphen&indicates a range of characters in a\\[-2pt]&&character class definition\\[1pt] [[~]]&tilde&complements a character class definition\\ \end{tabular}\par The metacharacters are defined in Section~\ref{metacharacterscode}. You may change these metacharacter assignments to suit your fancy by changing that section before tangling and compiling. \subsection{Character classes} Since the set of decimal digits, lowercase letters, and uppercase letters are used frequently, and since they are such long lists, a shorthand method of specifying [[[012\dots9]]], [[[abc\dots z]]], and [[[ABC\dots Z]]] exists. You may place a hyphen between the first and last characters. Thus, the pattern [[a[0-9]]] matches ``a0'', ``a1'', and so on. You need not specify the entire set of decimal digits, nor all of the letters when the shorthand notation is used. You may give [[[5-7]]], [[[a-g]]], and so on. The only restrictions are that the lower-valued character must be listed in front of of the hyphen. You may use the shorthand notation in a list of characters specifying a character class. Thus, [[[s12g5-7a-zA-Z$(]]] is a valid character class. The hyphen ([[-]]) has special meaning only when it falls between characters in a character class definition. If it appears at either end of the definition or outside such a definition, it has no special meaning. If the first character inside the left bracket is a tilde [[~]], it causes a match on any character except those listed. It is important to think of the character class as a single character position. If you need a literal [[[]] or [[]]] in a pattern, then escape it as [[:[]] or [[:]]], respectively. \par\medskip This command won't replace \textsf{grep} in anyone's toolbox, but it does have the advantage of working within the \bcpl/ interpreter \textsf{cinterp}. @ \eject\section{Literate Programs} This document not only describes the implementation of [[find]], it \textit{is} the implementation. The \textsf{noweb} system for ``literate programming'' generates both the document and the code from a single source. This source consists of interleaved prose and labelled code \textit{fragments}. The fragments are written in the order that best suits describing the program, namely the order you see in this document, not the order dictated by the \bcpl/ programming language. The program \textsf{noweave} accepts the source and produces the document's typescript, which includes all of the code and all of the text. The program \textsf{notangle} extracts all of the code, in the proper order for compilation. Fragments contain source code and references to other fragments. Fragment definitions are preceded by their labels in angle brackets. For example, the code\label{sample}% <>= sum := 0 FOR i = 1 TO 10 DO <> <>= sum := sum + x!i @ \noindent sums the elements of [[x]]. Several fragments may have the same name; \textsf{notangle} concatenates their definitions to produce a single fragment. \textsf{noweave} identifies this concatenation by using $+\equiv$ instead of $\equiv$ in continued definitions: <>= writef("%i*n", sum) @ \noindent{}Fragment definitions are like macro definitions; \textsf{notangle} extracts a program by expanding one fragment. If its definition refers to other fragments, they themselves are expanded, and so on. Fragment definitions include aids to help readers navigate among them. Each fragment name ends with the number of the page on which the fragment's definition begins and a letter giving its sequence within that page. If there is only one fragment on a page then there is no letter. This is also shown in the left margin. Each continued definition also shows the previous definition, and the next continued definition, if there is one. $\triangleleft$~7b is an example of a previous definition that appears on page~7, and 11~$\triangleright$ says the definition is continued on page~11. These annotations form a double linked list of definitions; the left arrow points to the previous definition in the list and the right arrow points to the next one. The previous link on the first definition is omitted, and the next link on the last definition is omitted. These lists are complete: If some of a fragment's definition appears on the same page with each other, the links refer to the page on which they appear. Fragments also show a list of pages on which the fragment is used, as illustrated by the (\pageref{sample}a) to the right of the definition for $\langle$\textit{increment sum}$\rangle$, above. @ \subsection{The program} This program is translated from the \textsf{Small-C} program of the same name appearing in the \textsf{Small-Tools} package by J.~E.~Hendrix. Translated into \bcpl/ the program has the usual structure. The fragment name consisting of an asterisk indicates to \textsf{noweb} that this is the \textit{root} fragment, which is expanded to generate the program. <<*>>= GET "libhdr" <> <> <> <> @ \subsection{The metacharacters}\label{metacharacterscode} All of the metacharacters are defined here. You may change them to suit your fancy before tangling and compiling. (And don't forget to change the documentation in Section~\ref{metacharactersdoc}.) <>= MANIFEST { Char = 'c' // identifies a character BoL = '`' // beginning of line EoL = '*'' // end of line Any = '?' // any character CCl = '[' // begin character class NCCl = '^' // negation of chracter class CClEnd = ']' // end of character class Closure = '**' // zero or more occurences Escape = ':' // escape character NotC = '^' // negation character } @ %def BoL EoL Any CCl NCCl CClEnd Closure Escape NotC @ \section{The [[start]] procedure} By convention execution of a \bcpl/ program begins with a call to [[start]]. In this program [[start]] processes its command line to get the pattern, compiles the pattern into an internal format, opens the input stream, searches for matching lines and prints them, and finally cleans up. <>= LET start() = VALOF { <> <> <> <> <> <> <> <> <> <> fin: <> <> RESULTIS 0 } @ %def start @ \subsection{Bailing out in case of trouble} We hope we don't have to, but just in case\dots <>= fin_p, fin_l := level(), fin @ <>= STATIC { fin_p; fin_l } @ %def fin_p fin_l @ \nohead Now we can get to the cleanup code from anywhere within the program. <>= longjump(fin_p, fin_l) @ \subsection{Processing the command line arguments} The first thing we have to do is to extract the arguments from the command line. The input stream and the pattern are required arguments, while an output stream is optional. <>= LET argv = VEC 10 @ %def argv @ \nohead We let [[rdargs]] process the command line for us. <>= IF 0 = rdargs("<>", argv, 10) DO <> @ \nohead We're going to get a little tricky with the line counter. If line numbering is not specified then we initialize the counter to [[-1]]. If line numbering is specified then we initialize the counter to the number of lines read so far (which is zero). From this point on, the line count will get incremented upon newline only if the count is nonnegative. <>= lcount := -1 - argv!3 @ \nohead We need to define the line counter. <>= STATIC { lcount } @ %def lcount @ \nohead We define the [[rdargs]] argument as a separate fragment because it will be passed on to [[error]] to tell the user what we expect on the command line. <>= FROM/A,TO/K,PAT/A/K,N/S @ \nohead Rather than merely telling the user his arguments are wrong, let's tell him what we expect. <>= error("Invalid args: FIND <>") @ \subsection{Compiling the pattern} The pattern must have been specified on the command line. <>= STATIC { pbuf = 0 } @ %def pbuf @ \nohead The user may have specified lines \textit{not} matching the pattern. <>= STATIC { invert = 0 } @ %def invert @ \nohead Now it's a matter of allocating the pattern buffer and compiling the pattern from the string given us on the command line. <>= pbuf := getvec(MaxPat) UNLESS pbuf DO <> <> IF '~' = argv!2%1 DO invert := -1 UNLESS makpat(argv!2) error("Pattern too long") @ \nohead In the unlikely event we run out of memory we want to let the user know why we terminated without doing any real work. <>= error("Insufficient memory") @ \nohead The pattern needs to be deallocated when we're done. <>= IF freevec DO freevec(pbuf) @ \subsection{Opening the input stream} The input stream must have been specified on the command line. <>= STATIC { instream = 0 } @ %def instream @ \nohead We attempt to find the file and if successful, select it for input. <>= instream := findinput(argv!0) UNLESS instream DO error("Can't open input") selectinput(instream) @ \subsection{Searching for matching lines} We open an output stream if one was specified, then print matching lines from the input stream. <>= <> <> @ \nohead The user may have specified an output stream. <>= STATIC { outstream = 0 } @ %def outstream @ \nohead If the user had specified an output stream then we find it and select it for output. <>= IF argv!1 DO { outstream := findoutput(argv!1) UNLESS outstream DO error("Can't open output") selectoutput(outstream) } @ \nohead We need a buffer to hold the input line. This buffer will be allocated when the command executes. <>= STATIC { lbuf = 0 } @ %def lbuf @ \nohead And we must not forget to deallocate it when we're done with it. <>= IF lbuf DO freevec(lbuf) @ \nohead Now we allocate the line buffer then read each line of input and see whether it matches. <>= lbuf := getvec(MaxLine + 1) UNLESS lbuf DO <> WHILE 0 <= readline() DO IF match() NEQV invert DO writeline() @ \subsection{Closing the streams} We close both the input stream and the output stream before terminating. <>= IF instream DO endread() IF outstream DO endwrite() @ \section{Input/Output} This command would be rather useless if it couldn't perform any input or output. This command works with individual lines. Even though patterns spanning multiple lines \textit{could} be given on the command line, such patterns will \textbf{not} work. The input line is stored unpacked (one character per word) in the variable [[lbuf]]. There is no length word; the line is terminated with a zero word. This means that the standard library routines such as [[writes]] cannot be used to write it out. It does mean that lines longer than 255 characters can be handled\dots as long as they're less than [[MaxLine]] characters. @ \subsection{Input} We will fold input lines longer than [[MaxLine]] characters. <>= MANIFEST { MaxLine = 1024 } @ %def MaxLine @ \nohead Since we're dealing with lines, we need a function to read a line from the input stream. The characters are placed unpacked into [[lbuf]]. A line is terminated by a newline or an end of stream, or if we reach [[MaxLine]] characters. Carriage returns are ignored, thus making the command virtually worthless for Mac-formatted files. But then, this is already rather worthless for true binary files. This function returns the number of characters read, or -1 if end of stream. <>= LET readline() = VALOF { LET i, ch = 0, ? UNLESS lcount < 0 DO lcount := lcount + 1 lbuf!2 := 0 WHILE i < MaxLine DO { <> <> } <> RESULTIS i - 1 } @ %def readline @ \nohead This should be pretty obvious. <>= ch := rdch() i := i + 1 lbuf!i := ch @ \nohead The character just read may terminate the line. <>= SWITCHON ch INTO { CASE endstreamch: IF 1 = i RESULTIS -1 CASE '*n': i := i - 1; BREAK CASE '*c': i := i - 1 default: ENDCASE } @ \nohead We want to make sure the line buffer is terminated properly. <>= i := i + 1 lbuf!i := 0 @ \subsection{Output} Since we're dealing with unpacked lines, we need a procedure to print one out. This is a lot easier than reading one since the only interpretation we have to apply to the line is searching for its end. And, since we stripped off the newline when we read it in, we have to tack one on at the end after writing it out. <>= AND writeline() BE { IF 0 <= lcount DO writef("%i5: ", lcount) FOR i = 1 TO MaxLine DO { UNLESS lbuf!i BREAK wrch(lbuf!i) } newline() } @ %def writeline @ \section{Pattern creation} We want to compile the pattern into an internal format to make the matching easier. The compiled pattern is placed unpacked into the vector [[pbuf]]. @ \nohead We have to put an upper limit on the size of the pattern. <>= MANIFEST { MaxPat = 257 } @ %def MaxPat @ \nohead Some symbolic names to help us deal with patterns. <>= MANIFEST { Count = 1; PrevCl; StartCl; CloSize } @ %def Count PrevCl StartCl CloSize @ \nohead Compile pattern specified by [[arg]] into pattern buffer [[pbuf]]. <>= LET makpat(arg) = VALOF { <> <> <> <> LET i, j, lastcl, lastj, lj, from = ?, 1, -1, 1, ?, ? i := 1 - invert from := i WHILE i <= arg%0 DO { lj := j TEST Any = arg%i THEN addset(Any, @j) ELSE TEST BoL = arg%i & i = from THEN addset(BoL, @j) ELSE TEST EoL = arg%i & 0 = arg%(i+1) THEN addset(EoL, @j) ELSE TEST CCl = arg%i THEN UNLESS getccl(arg, @i, @j) BREAK ELSE TEST Closure = arg%i & from < i THEN { <> } ELSE { <> } lastj := lj i := i + 1 } IF FALSE = addset(0, @j) | i < arg%0 RESULTIS FALSE RESULTIS TRUE } @ %def makpat @ \nohead A literal character is added to the pattern by flagging it as such and then adding the character. Remember that the character \textit{could} be an escaped character. <>= addset(Char, @j) addset(esc(arg, @i), @j) @ \nohead <>= lj := lastj IF BoL = pbuf!lj | EoL = pbuf!lj | Closure = pbuf!lj BREAK lastcl := stclos(@j, @lastj, lastcl) @ \nohead Put character [[c]] into pattern buffer [[pbuf]] and increment index [[!j]]. Returns [[FALSE]] if the pattern buffer is full. <>= LET addset(c, j) = VALOF { IF MaxPat <= !j RESULTIS FALSE pbuf!!j := c !j := !j + 1 RESULTIS TRUE } @ %def addset @ \nohead Map [[array%i]] into escaped character if appropriate. <>= AND esc(array, i) = VALOF { TEST Escape ~= array%!i RESULTIS array%!i ELSE TEST 0 = array%(!i+1) RESULTIS Escape ELSE { !i := !i + 1 SWITCHON array%!i INTO { CASE 't': RESULTIS '*t' CASE 'b': RESULTIS '*b' CASE 's': RESULTIS ' ' DEFAULT: RESULTIS array%!i } } } @ %def esc @ \nohead Expand character class at [[arg%i]] into [[pbuf!j]]. <>= AND getccl(arg, i, j) = VALOF { <> LET jstart = ? LET digit = "0123456789" LET loalf = "abcdefghijklmnopqrstuvwxyz" LET upalf = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" !i := !i + 1 TEST NotC = arg%!i THEN { addset(NCCl, j) !i := !i + 1 } ELSE addset(CCl, j) <> RESULTIS CClEnd = arg%!i } @ %def getccl @ \nohead Expand character class in [[arg]] into [[pbuf]]. <>= jstart := !j addset(0, j) WHILE arg%!i & CClEnd ~= arg%!i DO { TEST Escape = arg%!i THEN addset(esc(arg, i), j) ELSE TEST '-' ~= arg%!i THEN addset(arg%!i, j) ELSE TEST j <= 1 | 0 = arg%!i THEN addset('-', j) ELSE TEST '0' <= pbuf!(!j-1) <= '9' THEN dodash(digit, arg, i, j) ELSE TEST 'a' <= pbuf!(!j-1) <= 'z' THEN dodash(loalf, arg, i, j) ELSE TEST 'A' <= pbuf!(!j-1) <= 'Z' THEN dodash(upalf, arg, i, j) ELSE addset('-', j) !i := !i + 1 } pbuf!jstart := !j - jstart - 1 @ \nohead Insert closure entry at [[pbuf!j]]. <>= AND stclos(j, lastj, lastcl) = VALOF { LET jp, jt = ?, ? jp := !j - 1 WHILE !lastj <= jp DO { jt := jp + CloSize addset(pbuf!jp, @jt) jp := jp - 1 } !j := !j + CloSize jp := !lastj addset(Closure, lastj) addset(0, lastj) addset(lastcl, lastj) addset(0, lastj) RESULTIS jp } @ %def stclos @ \nohead Expand [[arg%(i-1) - arg%(i+1)]] into [[pbuf!j]] \dots <>= LET dodash(set, arg, i, j) BE { <> LET lower, upper = ?, ? !i := !i + 1 !j := !j - 1 upper := index(set, esc(arg, i)) lower := index(set, pbuf!!j) WHILE lower <= upper DO { addset(set%lower, j) lower := lower + 1 } } @ %def dodash @ \nohead Find character [[c]] in string [[s]]. <>= LET index(s, c) = VALOF { LET i = 1 WHILE s%i DO { IF s%i = c RESULTIS i i := i + 1 } RESULTIS -1 } @ %def index @ \section{Pattern matching} These procedures try to match the compiled pattern in [[pbuf]] against the input line in [[lbuf]]. @ \nohead This function tries to match a pattern anywhere in [[lbuf]]. <>= LET match() = VALOF { <> LET i = 1 WHILE TRUE DO { IF 0 <= amatch(i) RESULTIS TRUE i := i + 1 UNLESS lbuf!i RESULTIS FALSE } } @ %def match @ \nohead This function looks for a match starting at [[lbuf!from]]. <>= LET amatch(from) = VALOF { <> LET i, j, offset, stack = ?, 1, ?, -1 offset := from WHILE pbuf!j DO { TEST Closure = pbuf!j THEN { stack := j j := j + CloSize i := offset WHILE lbuf!i UNLESS omatch(@i, j) BREAK pbuf!(stack+Count) := i - offset pbuf!(stack+StartCl) := offset offset := i } ELSE UNLESS omatch(@offset, j) DO { WHILE 0 <= stack DO { IF 0 < pbuf!(stack+Count) BREAK stack := pbuf!(stack+PrevCl) } IF stack < 0 RESULTIS -1 pbuf!(stack+Count) := pbuf!(stack+Count) - 1 j := stack + CloSize offset := pbuf!(stack+StartCl) + pbuf!(stack+Count) } <> } RESULTIS offset } @ %def amatch @ \nohead Try to match a single pattern at [[pbuf!j]]. <>= LET omatch(i, j) = VALOF { <> LET bump = -1 TEST BoL = pbuf!j IF 1 = !i bump := 0 ELSE TEST EoL = pbuf!j UNLESS lbuf!!i bump := 0 ELSE TEST 0 = lbuf!!i RESULTIS FALSE ELSE TEST Char = pbuf!j IF lbuf!!i = pbuf!(j+1) bump := 1 ELSE TEST Any = pbuf!j bump := 1 ELSE TEST CCl = pbuf!j IF locate(lbuf!!i, j + 1) bump := 1 ELSE TEST NCCl = pbuf!j UNLESS locate(lbuf!!i, j + 1) bump := 1 ELSE error("In omatch: can't happen") IF 0 <= bump THEN { !i := !i + bump RESULTIS TRUE } RESULTIS FALSE } @ %def omatch @ \nohead Locate character [[c]] in character class beginning at [[offset]] <>= LET locate(c, offset) = VALOF { LET i = offset + pbuf!offset WHILE offset < i DO { IF c = pbuf!i RESULTIS TRUE i := i - 1 } RESULTIS FALSE } @ %def locate @ \nohead Determine the size of the entry at [[pbuf!j]] and increment [[j]] accordingly. <>= TEST Char = pbuf!j THEN j := j + 2 ELSE TEST BoL = pbuf!j | EoL = pbuf!j | Any = pbuf!j THEN j := j + 1 ELSE TEST CCl = pbuf!j | NCCl = pbuf!j THEN j := j + 2 + pbuf!(j+1) ELSE TEST Closure = pbuf!j THEN j := j + CloSize ELSE error("In amatch: can't happen") @ \section{Error reporting} Before we can print the error message we have to ensure that we're writing to the console where it can be seen by the user. Then we can print the message and tack on a newline. Rather than terminating the program here, we bail out to the cleanup code at the end of [[start]]. <>= LET error(msg) BE { selectoutput(findoutput("**")) writes(msg) newline() <> } @ %def error @ \section{Debugging} The program is functional enough for the author's purposes. However some debugging code is still left in the program. Once written, this code looked too good to just throw away. Conditional compilation keeps the code from being compiled into the final command but still available if needed in the future. To activate, remove the two slashes at the beginning of the first line of this code fragment. <>= //$$Debug $> $>Debug @ \subsection{The pattern buffer} When debugging it's useful to have the pattern buffer initialized to a known state. In this case the known state is all zeros, which is also the terminator. <>= $Debug @ \nohead This function dumps out the entire pattern buffer in hexadecimal. The buffer shouldn't come close to being filled up in normal use, and it should be properly terminated (we initialized it to be filled with zeros). However if some errant code should plant a zero into it we want to be able to see it. <>= LET DbgDumpPattBuf() BE { FOR i = 1 TO MaxPat DO { writef(" %x3", pbuf!i) UNLESS i REM 16 DO newline() } newline() } @ %def DbgDumpPattBuf @ \subsection{The input line} Just in case you ever suspect the weirdness is happening in the input, this procedure dumps out the input buffer in hexadecimal. This one does terminate on hitting a zero. <>= LET DbgDumpLineBuf() BE { LET i = 0 { i := i + 1 writef(" %x3", lbuf!i) UNLESS i REM 16 DO newline() } REPEATWHILE lbuf!i newline() } @ %def DbgDumpLineBuf @ \subsection{Calling the debug procedures} And we'll provide conditional calls to these dump procedures. These fragments may be sprinkled in wherever needed. <>= $Debug @ \nohead <>= $Debug @ \cleardoublepage\appendix\section{Index of Fragments} References are to the first definition of each fragment. \begin{multicols}{2}\nowebchunks\end{multicols}% \eject\section{Index of Identifiers} Underlined entries are their definitions. Standard library definitions are not listed here. Nor are [[FOR]] control variables and most other variables local to a procedure. \begin{multicols}{2}\nowebindex\end{multicols}% \eject\pagestyle{empty}\tableofcontents \end{document}