home *** CD-ROM | disk | FTP | other *** search
/ Between Heaven & Hell 2 / BetweenHeavenHell.cdr / 300 / 295 / indexer.pas < prev    next >
Pascal/Delphi Source File  |  1985-06-22  |  25KB  |  836 lines

  1. program indexer(input,output);
  2.  
  3. {         INDEX  CREATION  FROM  THE  KEYBOARD          }
  4. {                                  }
  5. {    David E. Cortesi, 2340 Tasso St., Palo Alto CA 94301.    }
  6. {           (compuserve 72155,450)              }
  7. {                                  }
  8. { Accepts index entries for a book from the keyboard, sorts   }
  9. { the entries and sub-entries, collates page references,      }
  10. { and creates an ASCII file that can be printed or edited.    }
  11. {                                  }
  12. { Term Recall is an unusual feature of the user interaction.  }
  13. { If, when entering an index term, the user hits the ESC key, }
  14. { the program will find the least term that matches the input }
  15. { to that point and fill in its characters on the input line. }
  16. { Hitting ESC again retracts those letters and displays the   }
  17. { letters of the next-higher matching term.  This can save    }
  18. { considerable typing -- a long term can be entered as only   }
  19. { a couple of letters plus ESC -- and it allows the user to   }
  20. { review the terms entered to that point in alpha order.      }
  21. {                                  }
  22. { Creates files INDEXER.OUT, the index-document file, and     }
  23. { INDEXER.TRE, an internal record of the tree which will be   }
  24. { reloaded on the next run if it then exists.              }
  25. {-------------------------------------------------------------}
  26.  
  27. const
  28.     nullch = 0;         { the null, end-of-string      }
  29.     strmax = 65;        { max size of a string (64,00h)}
  30.     sbufsize = 2046;        { page size of a string buffer }
  31.     sbufnum = 16;        { allow up to 32K of buffers   }
  32.     maxdepth = 20;        { stack size for tree-walks    }
  33.     asciibel = 7;        { names for ascii characters   }
  34.     asciibs = 8;
  35.     asciilf = 10;
  36.     asciicr = 13;
  37.     asciiesc = 27;
  38.     asciiblank = 32;
  39.     asciidel = 127;
  40.  
  41. type
  42.     strindex = 1..strmax;    { indices over strings           }
  43.     strlength= 0..strmax;    { lengths of strings           }
  44.     relation = (less,equal,more); { result of comparisons      }
  45.     nchar = 0..255;        { numeric characters are bytes }
  46.     str = record        { an independent string is     }
  47.         len : strlength;    { ..a length and some bytes,   }
  48.         val : array[strindex] of nchar  { ending in 00h    }
  49.         end;
  50.     pstrb   = ^strbuff;
  51.     strbuff = record        { a string buffer is a compact }
  52.         free : 0..sbufsize; { collection of strings.       }
  53.         data : array[1..sbufsize] of nchar
  54.         end;
  55.     stref = record        { an indirect string is the    }
  56.         nb : 1..sbufnum;    { index of an strbuff's address}
  57.         bo : 1..sbufsize    { and an index into it.        }
  58.         end;
  59.     pprec = ^prec;
  60.     prec = record        { a page on which a term is    }
  61.         next : pprec;    { ..referenced, and ^next one  }
  62.         num  : integer
  63.         end;
  64.     pnode = ^node;
  65.     node = record        { one node of a binary tree    }
  66.         lson, rson,     { descendant trees           }
  67.         subt : pnode;    { subtree of sub-terms           }
  68.         iref, uref : stref; { original and uppercase terms }
  69.         phead : pprec;    { head of chain of page-refs   }
  70.         skip : boolean;    { phony node "M" starts a tree }
  71.         end;
  72.     treewalk = record        { current state of an inorder  }
  73.         current : pnode;    { ..walk of a tree: this node, }
  74.         top : 0..maxdepth;    { stack-top pointer, stacked   }
  75.         stack : array[1..maxdepth] of pnode;{ nodes, mark  }
  76.         goneleft : boolean    { true when backing out of leaf}
  77.         end;
  78.  
  79. var
  80.     sbufptrs : array[1..sbufnum] of pstrb; { blocks of bytes}
  81.     sbufcnt  : 0..sbufnum;    { how many blocks are active   }
  82.     maintree : pnode;        { root of the term-tree        }
  83.     initerm  : str;        { "M" term for starting trees  }
  84.     indlevel : 0..9;        { subterm nesting (indent) lev.}
  85.     outfile  : text;        { the output document           }
  86.  
  87. {-------------------------------------------------------------}
  88. {     routines operating on independent strings          }
  89. { Pascal/Z string type was avoided to maximize portability.   }
  90. {-------------------------------------------------------------}
  91.  
  92. function upcase(c:nchar) : nchar;
  93.     { force character to uppercase }
  94.     begin
  95.     if (c>=ord('a')) and (c<=ord('z')) then
  96.         upcase := c-32
  97.     else
  98.         upcase := c
  99.     end;
  100.  
  101. procedure stucase(var a,b:str);
  102.     { duplicate a string, forcing uppercase }
  103.     var j : strlength;
  104.     c : nchar;
  105.     begin
  106.     j := 0;
  107.     repeat
  108.         j := j+1;
  109.         c := a.val[j];
  110.         b.val[j] := upcase(c);
  111.     until c=nullch;
  112.     b.len := j-1
  113.     end;
  114.  
  115. {-------------------------------------------------------------}
  116. {        routines operating on stored strings          }
  117. { To keep all stored terms in string form (P/Z or our version)}
  118. { would use far too much storage. Here we pack strings into   }
  119. { large blocks.  The blocks are allocated as needed, to a max }
  120. { of 32K -- limit enforced by compiler range checking.          }
  121. {-------------------------------------------------------------}
  122.  
  123. procedure stput(var a:str; var b:stref);
  124.     { stow string a in latest buffer, return indirect reference}
  125.     var bp : pstrb;
  126.     j : strindex;
  127.     k : 1..sbufsize;
  128.     begin
  129.     bp := sbufptrs[sbufcnt]; { ^latest string buffer       }
  130.     if bp^.free<(a.len+1) then begin { not enough room!    }
  131.         new(bp);        { make, count new buffer page  }
  132.         sbufcnt := sbufcnt+1; { range error here when full }
  133.         sbufptrs[sbufcnt] := bp;
  134.         bp^.free := sbufsize
  135.     end;
  136.  
  137.     b.nb := sbufcnt;    { save buffer-page number      }
  138.     j := 1;
  139.     k := 1+sbufsize-bp^.free;
  140.     b.bo := k;        { save buffer-page offset      }
  141.  
  142.     while j <= a.len do begin
  143.         bp^.data[k] := a.val[j];
  144.         j := j+1;
  145.         k := k+1
  146.     end;
  147.     bp^.data[k] := nullch;    { mark end of stored string    }
  148.     bp^.free := sbufsize-k    { adjust bytes left in block   }
  149.     end;
  150.  
  151. procedure stget(var b:stref; var a:str);
  152.     { retrieve stored string from buffer into string-record }
  153.     var bp : pstrb;
  154.     j : strindex;
  155.     k : 1..sbufsize;
  156.     c : nchar;
  157.     begin
  158.     bp := sbufptrs[b.nb];    { point to the buffer page     }
  159.     k := b.bo;        { ..and offset into it           }
  160.     j := 1;
  161.     repeat            { copy the stored string out   }
  162.         c := bp^.data[k];
  163.         a.val[j] := c;
  164.         j := j+1;
  165.         k := k+1;
  166.     until (c=nullch);
  167.     a.len := j-2
  168.     end;
  169.  
  170. function sbcomp(var a:str; var b:stref) : relation;
  171.     { EXACT comparison of a string to a stored string value --
  172.       if "a" is initially equal but shorter, it is "less." }
  173.     var bp : pstrb;
  174.     j  : strindex;
  175.     k  : 1..sbufsize;
  176.     x,y : nchar;
  177.     r  : relation;
  178.     begin
  179.     bp := sbufptrs[b.nb];
  180.     k := b.bo;
  181.     j := 1;
  182.     repeat
  183.         x := a.val[j];
  184.         y := bp^.data[k];
  185.         j := j+1;
  186.         k := k+1
  187.     until (x<>y) or (x=nullch);
  188.     if x=y then r := equal
  189.     else if x<y then r := less
  190.          else     r := more;
  191.     sbcomp := r
  192.     end;
  193.  
  194. function sxcomp(var a:str; var b:stref) : relation;
  195.     { APPROXIMATE comparison of a string to a stored string --
  196.      if "a" is initially equal but shorter, it is "equal." }
  197.   var bp : pstrb;
  198.     j  : strindex;
  199.     k  : 1..sbufsize;
  200.     x,y : nchar;
  201.     r  : relation;
  202.     begin
  203.     bp := sbufptrs[b.nb];
  204.     k := b.bo;
  205.     j := 1;
  206.     repeat
  207.         x := a.val[j];
  208.         y := bp^.data[k];
  209.         j := j+1;
  210.         k := k+1
  211.     until (x<>y) or (x=nullch);
  212.     if (x=y) or (x=nullch) then r := equal
  213.     else if x<y then r := less
  214.          else     r := more;
  215.     sxcomp := r
  216.     end;
  217.  
  218. {-------------------------------------------------------------}
  219. {        routines operating on the binary trees          }
  220. { Each tree node represents one index term.  The term itself  }
  221. { is stored two ways, as typed and all-caps.  The latter is   }
  222. { used for comparison of terms, so that "Apple" = "apple".    }
  223. { A node anchors a sorted chain of page-numbers, and may hold }
  224. { the root of an independent sub-tree of sub-terms.  The tree }
  225. { is ordered so that all terms off the .lson are less than,   }
  226. { and all terms off the .rson are greater, than this term.    }
  227. {-------------------------------------------------------------}
  228.  
  229. function makenode(var a, ua : str) : pnode;
  230.     { make a new tree node given term-strings }
  231.     var tn : pnode;
  232.     begin
  233.     new(tn);
  234.     tn^.lson := nil;
  235.     tn^.rson := nil;
  236.     tn^.subt := nil;
  237.     stput(a,tn^.iref);
  238.     stput(ua,tn^.uref);
  239.     tn^.phead := nil;
  240.     tn^.skip := false;
  241.     makenode := tn
  242.     end;
  243.  
  244. procedure startree(var t:pnode);
  245.     { begin a tree with an artificial node whose term
  246.        is "M" to encourage early balance }
  247.     begin
  248.        t := makenode(initerm,initerm);
  249.        t^.skip := true
  250.     end;
  251.  
  252. function insert(tree:pnode; var a:str) : pnode;
  253.     { put a new term into a tree, or find it if it is there.
  254.        either way, return the term's node's address.         }
  255.     var o,p,q : pnode;
  256.     ua    : str;
  257.     r     : relation;
  258.     begin
  259.     stucase(a,ua);
  260.     p := tree;
  261.  
  262.     repeat
  263.         r := sbcomp(ua,p^.uref);
  264.         if r<>equal then
  265.         if r=less then q := p^.lson
  266.         else           q := p^.rson
  267.         else q := p;
  268.         o := p;
  269.         p := q
  270.     until (r=equal) or (p=nil);
  271.  
  272.     if r=equal then insert := p
  273.     else begin { term doesn't exist in the tree }
  274.         q := makenode(a,ua);
  275.         if r=less then o^.lson := q
  276.         else       o^.rson := q;
  277.         insert := q
  278.     end;
  279. end;
  280.  
  281. {-------------------------------------------------------------}
  282. { routines for tree-walking.  These routines abstract the     }
  283. { idea of an in-order tour of the tree into a single record.  }
  284. { The usual algorithm for a walk is recursive (see J&W 11.5), }
  285. { which is not convenient for this program.              }
  286. {-------------------------------------------------------------}
  287.  
  288. procedure initwalk(t:pnode; var w:treewalk);
  289.     { initialize for a walk over the given tree }
  290.     begin
  291.     w.current := t;     { start at the top node,       }
  292.     w.goneleft := false;    { ..but descend left first off }
  293.     w.top := 0        { stack is empty           }
  294.     end;
  295.  
  296. procedure push(pn: pnode; var w: treewalk);
  297.     { push a given node onto the walk-stack }
  298.     begin
  299.     if w.top<maxdepth then begin
  300.         w.top := w.top+1;
  301.         w.stack[w.top] := pn
  302.     end
  303.     end;
  304.  
  305. function pop(var w:treewalk) : pnode;
  306.     { pop the top node from the walk-stack }
  307.     begin
  308.     if w.top>0 then begin
  309.         pop := w.stack[w.top];
  310.         w.top := w.top-1
  311.     end
  312.     else pop := nil
  313.     end;
  314.  
  315. function treestep(var w:treewalk) : pnode;
  316.     { step to the next node in lexical order in a tree.
  317.     return that node as result, and save it in the walk
  318.     record as "current."  Return nil if end of tree.       }
  319.     var t : pnode;
  320.     begin
  321.     t := w.current;
  322.     repeat
  323.         if not w.goneleft then begin { descend to the left }
  324.         if t<> nil then
  325.             while t^.lson<>nil do begin
  326.             push(t,w);
  327.             t := t^.lson
  328.             end;
  329.         w.goneleft := true { t^ a left-leaf of tree }
  330.         end
  331.         else { been down; have handled current; go up/right}
  332.         if t<> nil then
  333.             if t^.rson <> nil then begin
  334.             t := t^.rson;         { jog right, then }
  335.             w.goneleft := false  { drop down again }
  336.             end
  337.             else { nowhere to go but up }
  338.             t := pop(w)
  339.     until w.goneleft; { repeats when we jog right }
  340.     w.current := t;
  341.     treestep := t
  342.     end;
  343.  
  344. function setscan(tree: pnode; var w: treewalk; var a: str)
  345.                          : pnode;
  346.     { given a partial term "a," a tree "tree," and a tree-
  347.     walk record "w," set up w so that a series of calls on
  348.     function treestep will return all the nodes that are
  349.     initially equal to a in ascending order.  If there are
  350.     none such, return nil.  This function sets up for Term
  351.     Recall when the escape key is pressed during input.
  352.  
  353.     The algorithm is to find the matching term that is
  354.     highest in the tree, then use treestep to find the
  355.     lexically-least node under that term (which may not be
  356.     a match) and then to treestep to the first match.}
  357.  
  358.     var ua : str;
  359.     p,t : pnode;
  360.     r : relation;
  361.     quit : boolean;
  362.     begin
  363.     stucase(a,ua);
  364.     initwalk(tree,w);
  365.     t := tree;
  366.     if t=nil then setscan := nil  { no matches possible    }
  367.     else begin
  368.         { step 1 is to find any part-equal node at all     }
  369.         quit := false;
  370.         repeat
  371.         r := sxcomp(ua,t^.uref);
  372.         case r of
  373.             less : if t^.lson<>nil then t := t^.lson
  374.                        else quit := true;
  375.             more : if t^.rson<>nil then t := t^.rson
  376.                        else quit := true;
  377.             equal : quit := true
  378.         end
  379.         until quit;
  380.         { If we have a match, it may not be the least one.
  381.           If this node has a left-son, there can be lesser
  382.           matches (and nonmatches) down that branch. }
  383.         if r<>equal then setscan := nil { no match a-tall  }
  384.         else begin
  385.         w.current := t;
  386.         if t^.lson=nil then w.goneleft := true
  387.         else begin { zoom down in tree }
  388.             w.goneleft := false;
  389.             repeat
  390.             t := treestep(w);
  391.             r := sxcomp(ua,t^.uref)
  392.             until r=equal
  393.         end;
  394.         setscan := t
  395.         end
  396.     end
  397.     end;
  398.  
  399. {-------------------------------------------------------------}
  400. {        routines for phase 1 -- input              }
  401. {-------------------------------------------------------------}
  402.  
  403. procedure indent;
  404.     { indent the cursor for the current nesting level }
  405.     var i : 0..9;
  406.     begin
  407.     for i := 1 to indlevel do write('. . ')
  408.     end;
  409.  
  410. function DOSXQQ(cmd,prm:word) : byte; EXTERN;
  411.  
  412. function readnc : nchar;
  413.     { get one byte from the keyboard, bypassing the
  414.       usual pascal procedures and going straight to DOS }
  415.  
  416.  { #8 is dos: wait for key, no echo, do check break }
  417.  
  418.     begin
  419.     readnc := RETYPE(nchar,DOSXQQ(8,0));
  420.     end;
  421.  
  422. procedure getterm(tree: pnode; var a:str; var cont: boolean);
  423.     { get a term from the user, with control keys used thus:
  424.     cr : end the term.
  425.     lf : end the term, begin a subterm of it.
  426.     esc: try to complete the term with the next (first)
  427.          matching term from the present tree-context.
  428.     del: cancel esc-completion, return to original entry.  }
  429.     var
  430.     c    : nchar;
  431.     j, oj    : strindex;
  432.     k    : strlength;
  433.     x,ua    : str;
  434.     quit    : boolean;
  435.     tw    : treewalk;
  436.     p    : pnode;
  437.  
  438.     procedure backup;
  439.     { backup the screen and the "a" string to the original
  440.         term that was entered. }
  441.     var qj    : strindex;
  442.     begin
  443.         for qj := j downto (oj+1) do
  444.           write(chr(asciibs),chr(asciiblank),chr(asciibs));
  445.         j := oj;
  446.         a.val[j] := nullch
  447.     end;
  448.  
  449.     procedure startscan;
  450.     { set up for an alphabetical scan over all terms that
  451.       are an initial match to user entry thus far.    Setscan
  452.       does most of the work. }
  453.     begin
  454.         stucase(a,ua); { for stepscan's benefit }
  455.         p := setscan(tree,tw,a);
  456.         if p<>nil then { phony node only if a.len=0 }
  457.         if p^.skip then p := treestep(tw);
  458.         if p<>nil then begin { this node has to be equal }
  459.         stget(p^.iref,x);
  460.         k := x.len+1
  461.         end
  462.         else k := 0
  463.     end;
  464.  
  465.     procedure stepscan;
  466.     { find the next match to the original string, leaving
  467.       its value in x, or k=0 if there is none.  }
  468.     begin
  469.         k := 0;
  470.         p := treestep(tw);
  471.         if p<>nil then
  472.         if p^.skip then p := treestep(tw);
  473.         if p<>nil then
  474.         if equal=sxcomp(ua,p^.uref) then begin
  475.             stget(p^.iref,x);
  476.             k := x.len+1
  477.         end
  478.     end;
  479.  
  480.     begin { the main Get Term procedure }
  481.     indent; write('term: ');
  482.     j := 1; oj := j;    { no data in the a-string      }
  483.     k := 0;         { no esc-scan working           }
  484.     quit := false;        { not finished yet (hardly!)   }
  485.     repeat
  486.         a.val[j] := nullch; { keep "a" a finished string   }
  487.         a.len := j-1;    { ..at all times           }
  488.         c := readnc;
  489.         case c of
  490.  
  491.         asciibs :        { destructive backspace        }
  492.         if j>1 then begin
  493.             write(chr(asciibs),chr(asciiblank),chr(asciibs));
  494.             j := j-1;
  495.             oj := j;    { the current scan is accepted }
  496.             k := 0;    { ..and no scan is underway    }
  497.         end;
  498.  
  499.         asciicr :        { normal completion           }
  500.         begin
  501.             write(chr(asciicr),chr(asciilf));
  502.             quit := true
  503.         end;
  504.  
  505.         asciilf :        { complete, move on to subterm }
  506.         begin
  507.             write(chr(asciicr),chr(asciilf));
  508.             quit := true
  509.         end;
  510.  
  511.         asciiesc :        { automatic scan for match     }
  512.         begin
  513.             backup;    { wipe rejected match if any   }
  514.             if k=0 then startscan else stepscan;
  515.             if k=0 then { no (further) match found     }
  516.             write(chr(asciibel))
  517.             else    { next (first?) match found    }
  518.             while j<k do begin
  519.                 a.val[j] := x.val[j];
  520.                 write(chr(a.val[j]));
  521.                 j := j+1
  522.             end
  523.         end;
  524.  
  525.         asciidel :        { cancel search for match      }
  526.         begin
  527.             backup;
  528.             k := 0    { no active scan           }
  529.         end;
  530.  
  531.         otherwise        { ordinary (?) character       }
  532.         if (c<asciiblank) or (j=strmax) then
  533.             write(chr(asciibel))
  534.         else begin
  535.             write(chr(c));
  536.             a.val[j] := c;
  537.             j := j+1;
  538.             oj := j;     { the current scan has been   }
  539.             k := 0     { ..accepted and is over      }
  540.         end
  541.         end {case}
  542.     until quit;
  543.     cont := c=asciilf
  544.     end;
  545.  
  546. procedure getpage(var i: integer);
  547.     { read a page number into an integer.  If page numbers
  548.       are not simple integers, eg "3-17" and the like, this
  549.       routine would have to build a string. }
  550.     begin
  551.     indent;
  552.     write('page: ');
  553.     readln(i)
  554.     end;
  555.  
  556. procedure makepage(var p:pprec; i:integer);
  557.     { make a page record and install its address }
  558.     begin
  559.     new(p);
  560.     p^.next := nil;
  561.     p^.num    := i
  562.     end;
  563.  
  564. procedure addpage(np: pnode; pg: integer);
  565.     { add a page number to the chain off a node.  This is
  566.       a classic case of an algorithm that requires a 2-exit
  567.       loop; the scan of the chain has to stop when a higher
  568.       page number is found OR when the end of the chain is
  569.       reached.    It could be done with Repeat or While, but
  570.       it actually looks cleaner with Goto. }
  571.     label 99,101,102,103;
  572.     var p1, p2, p3: pprec;
  573.     begin
  574.     p1 := np^.phead;
  575.     if p1=nil then makepage(np^.phead,pg)
  576.     else  { some pages already noted, search chain }
  577.         if pg<p1^.num then begin
  578.         makepage(p2,pg); { this page less than all }
  579.         p2^.next := p1;
  580.         np^.phead := p2
  581.         end
  582.         else begin { this page goes somewhere in chain }
  583.         99: p2 := p1^.next;
  584.         if p2=nil then goto 101;
  585.         if pg<p2^.num then goto 102;
  586.         p1 := p2;
  587.         goto 99;
  588.         101: { p1^ last number in chain, pg is => it }
  589.         begin
  590.             if pg>p1^.num then
  591.             makepage(p1^.next,pg);
  592.             goto 103
  593.         end;
  594.         102: {p1^.num <= pg <p2^.num; pg goes between }
  595.         begin
  596.             if pg>p1^.num then begin
  597.             makepage(p3,pg);
  598.             p3^.next := p2;
  599.             p1^.next := p3
  600.             end
  601.         end;
  602.         103: ;
  603.         end
  604.     end;
  605.  
  606. procedure load(var atree:pnode);
  607.     { input control: load terms into a tree from the keyboard.
  608.       the code is recursive; if the user wants to do a subterm
  609.       this routine calls itself to load the sub-tree of the
  610.       superior term's node.  A page number of zero is a disaster
  611.       when we reload the saved tree, so one is converted to -1.}
  612.     var aterm : str;
  613.     anode : pnode;
  614.     apage : integer;
  615.     cont  : boolean;
  616.     begin
  617.     repeat
  618.         getterm(atree,aterm,cont);
  619.         if aterm.len>0 then begin
  620.         anode := insert(atree,aterm);
  621.         if not cont then begin
  622.             getpage(apage);
  623.             if apage=0 then apage := 32767;
  624.             addpage(anode,apage)
  625.         end
  626.         else begin { user hit lf, wants to recurse }
  627.             if anode^.subt=nil then
  628.             startree(anode^.subt);
  629.             indlevel := indlevel+1;
  630.             load(anode^.subt);
  631.             indlevel := indlevel-1
  632.         end
  633.         end;
  634.     until (aterm.len=0) or (indlevel>0)
  635.     end;
  636.  
  637. {-------------------------------------------------------------}
  638. {           routines for phase 2 -- output              }
  639. {-------------------------------------------------------------}
  640.  
  641. procedure filenode(np: pnode; var oc: nchar);
  642.     { write one node's contents, term + pages, to the output.
  643.       It is at this level that we insert a blank line on a break
  644.       in the sequence of main-term initial letters.  Once more,
  645.       a loop over an ordered chain is cleaner with Goto. }
  646.     label 99;
  647.     var a : str;
  648.     p : pprec;
  649.     i : 0..9;
  650.     j : strindex;
  651.     k1, k2 : integer;
  652.     ic : nchar;
  653.     begin
  654.     if not np^.skip then begin { ignore phony nodes }
  655.         stget(np^.iref,a);
  656.         ic := upcase(a.val[1]);
  657.         if (indlevel=0) and  { main-term initial change? }
  658.          (oc<>ic) then writeln(outfile);
  659.         oc := ic;
  660.         for i := 1 to indlevel do write(outfile,'    ');
  661.         for j := 1 to a.len do write(outfile,chr(a.val[j]));
  662.         p := np^.phead;
  663.         while p<>nil do begin
  664.         write(outfile,' ');
  665.         k1 := p^.num;
  666.         k2 := k1+1;
  667.          99:p := p^.next;    { elide sequential numbers     }
  668.         if p<>nil then
  669.             if p^.num=k2 then begin
  670.             k2 := k2+1;
  671.             goto 99
  672.             end;
  673.         write(outfile,k1:1); { write "17" or "17-19"   }
  674.         if (k1+1)<k2 then write(outfile,'-',k2-1:1);
  675.         if p<>nil then write(outfile,',');
  676.         end;
  677.     writeln(outfile);
  678.     end
  679.     end;
  680.  
  681. procedure filetree(intree: pnode);
  682.     { walk through a (sub-) tree and write each node }
  683.     var tree    : pnode;
  684.     tw    : treewalk;
  685.     oc    : nchar;
  686.     begin
  687.     oc := nullch;
  688.     initwalk(intree,tw);
  689.     tree := treestep(tw);
  690.     while tree<>nil do begin
  691.         filenode(tree,oc);
  692.         if tree^.subt<>nil then begin
  693.         indlevel := indlevel+1;
  694.         filetree(tree^.subt);
  695.         indlevel := indlevel-1
  696.         end;
  697.         tree := treestep(tw)
  698.     end
  699.     end;
  700.  
  701. procedure dump;
  702.     begin
  703.     assign(outfile,'INDEXER.OUT');
  704.     rewrite(outfile);
  705.     filetree(maintree)
  706.     end;
  707.  
  708. {-------------------------------------------------------------}
  709. {       routines for phase 0 -- initialization          }
  710. {-------------------------------------------------------------}
  711.  
  712. procedure init;
  713.     { initialize the various mechanisms }
  714.     begin
  715.     indlevel := 0;
  716.     new (sbufptrs[1]);
  717.     sbufcnt := 1;
  718.     sbufptrs[1]^.free := sbufsize;
  719.     initerm.val[1] := ord('M');
  720.     initerm.val[2] := nullch;
  721.     initerm.len := 1;
  722.     startree(maintree);
  723.     end;
  724.  
  725. procedure loadall;
  726.     { if a saved-tree file INDEXER.TRE exists, load its values
  727.     into the tree.        }
  728.     var loadtree : file of nchar;
  729.  
  730.     procedure reload(t:pnode);
  731.     { reload one (sub-)tree from the saved-tree file }
  732.     { the recorded form of one node of a tree is:
  733.         termlength (1..strmax-1),
  734.         that many term bytes in reverse order,
  735.         page numbers as high byte, low byte,
  736.         page number of (zero,zero).
  737.     the file is a sequence of terms as above. a tree ends
  738.     with a byte of zero.  a sub-tree is introduced with a
  739.     byte of strmax.                        }
  740.  
  741.     var
  742.     x   : str;
  743.     j,fj: strindex;
  744.     p   : pnode;
  745.     k   : integer;
  746.     k1,k2 : 0..255;
  747.  
  748.     begin
  749.         read(loadtree,j);
  750.         while j<>nullch do begin
  751.         x.len := j;
  752.         for fj := j downto 1 do read(loadtree,x.val[fj]);
  753.         x.val[j+1] := nullch;
  754.         p := insert(t,x);
  755.         repeat
  756.             read(loadtree,k1,k2);
  757.             k := (k1*256)+k2;
  758.             if k<>0 then addpage(p,k)
  759.         until k=0;
  760.         read(loadtree,j);
  761.         if j=strmax then begin { a sub-tree }
  762.             startree(p^.subt);
  763.             reload(p^.subt);
  764.             read(loadtree,j)
  765.         end
  766.         end
  767.     end;
  768.  
  769.     begin
  770.     assign(loadtree,'INDEXER.TRE');
  771.     loadtree.TRAP := TRUE; { DO NOT ABORT ON MISSING FILE }
  772.     reset(loadtree);
  773.     if loadtree.ERRS = 0 then reload(maintree);
  774.     end;
  775.  
  776. {-------------------------------------------------------------}
  777. {          routines for phase 3 -- termination          }
  778. {-------------------------------------------------------------}
  779.  
  780. procedure saveall;
  781.     { save the term-tree in the file INDEXER.TRE so it can
  782.     be reloaded for additions later, if need be. }
  783.     var savetree    : file of nchar;
  784.     x   : str;
  785.  
  786.     procedure unload(t:pnode);
  787.     { dump the contents of a (sub-) tree to disk in
  788.         "preorder," a sequence such that the exact layout
  789.         of the tree will be reconstructed if the tree is
  790.         reloaded from the file. }
  791.     label 99;
  792.     var j    : strindex;
  793.         p    : pprec;
  794.         k    : integer;
  795.         k1, k2 : nchar;
  796.     begin
  797.         if t^.skip then goto 99; { dump not the phony node }
  798.         stget(t^.iref,x);
  799.         write(savetree,x.len);
  800.         for j:=x.len downto 1 do write(savetree,x.val[j]);
  801.         p := t^.phead;
  802.         while p<>nil do begin
  803.         k := p^.num;
  804.         k1 := k div 256; k2 := k mod 256;
  805.         write(savetree,k1,k2);
  806.         p := p^.next
  807.         end;
  808.         write(savetree,nullch,nullch); { flag end of pages }
  809.         if t^.subt<>nil then begin
  810.         write(savetree,strmax);{ flag start of subtree }
  811.         unload(t^.subt);
  812.         write(savetree,nullch) { flag end of subtree }
  813.         end;
  814.     99: if t^.lson<>nil then unload(t^.lson);
  815.         if t^.rson<>nil then unload(t^.rson);
  816.     end;
  817.  
  818.     begin
  819.     assign(savetree,'INDEXER.TRE');
  820.     rewrite(savetree);
  821.     unload(maintree);
  822.     write(savetree,nullch)    { flag end of main tree }
  823.     end;
  824.  
  825. {-------------------------------------------------------------}
  826. { The main program, at last.....                  }
  827. {-------------------------------------------------------------}
  828.  
  829. begin
  830.     init;
  831.     loadall;
  832.     load(maintree);
  833.     saveall;
  834.     dump
  835. end.
  836.