home *** CD-ROM | disk | FTP | other *** search
/ CP/M / CPM_CDROM.iso / simtel / sigm / vols000 / vol094 / indexer.pas < prev    next >
Pascal/Delphi Source File  |  1984-04-29  |  26KB  |  836 lines

  1. program indexer; {$c-,e+,f-,i-,j-,m-,p+,r+,s+,t-,u+          }
  2. {-------------------------------------------------------------}
  3. {                                  }
  4. {         INDEX  CREATION  FROM  THE  KEYBOARD          }
  5. {                                  }
  6. {    David E. Cortesi, 2340 Tasso St., Palo Alto CA 94301.    }
  7. {           (compuserve 72155,450)              }
  8. {                                  }
  9. { Accepts index entries for a book from the keyboard, sorts   }
  10. { the entries and sub-entries, collates page references,      }
  11. { and creates an ASCII file that can be printed or edited.    }
  12. {                                  }
  13. { Term Recall is an unusual feature of the user interaction.  }
  14. { If, when entering an index term, the user hits the ESC key, }
  15. { the program will find the least term that matches the input }
  16. { to that point and fill in its characters on the input line. }
  17. { Hitting ESC again retracts those letters and displays the   }
  18. { letters of the next-higher matching term.  This can save    }
  19. { considerable typing -- a long term can be entered as only   }
  20. { a couple of letters plus ESC -- and it allows the user to   }
  21. { review the terms entered to that point in alpha order.      }
  22. {                                  }
  23. { Creates files INDEXER.OUT, the index-document file, and     }
  24. { INDEXER.TRE, an internal record of the tree which will be   }
  25. { reloaded on the next run if it then exists.              }
  26. {-------------------------------------------------------------}
  27.  
  28. const
  29.    nullch = 0;               { the null, end-of-string      }
  30.    strmax = 65;            { max size of a string (64,00h)}
  31.    sbufsize = 2046;           { page size of a string buffer }
  32.    sbufnum = 16;           { allow up to 32K of buffers   }
  33.    maxdepth = 20;           { stack size for tree-walks    }
  34.    asciibel = 7;           { names for ascii characters   }
  35.    asciibs = 8;
  36.    asciilf = 10;
  37.    asciicr = 13;
  38.    asciiesc = 27;
  39.    asciiblank = 32;
  40.    asciidel = 127;
  41.  
  42. type
  43.    strindex = 1..strmax;       { indices over strings          }
  44.    strlength= 0..strmax;       { lengths of strings          }
  45.    relation = (less,equal,more); { result of comparisons      }
  46.    nchar = 0..255;           { numeric characters are bytes }
  47.    str = record            { an independent string is     }
  48.        len : strlength;    { ..a length and some bytes,   }
  49.        val : array[strindex] of nchar  { ending in 00h    }
  50.        end;
  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.    page = record           { a page on which a term is    }
  60.        next : ^page;       { ..referenced, and ^next one  }
  61.        num    : integer
  62.        end;
  63.    ppage = ^page;
  64.    node = record           { one node of a binary tree    }
  65.        lson, rson,           { descendant trees          }
  66.        subt : ^node;       { subtree of sub-terms          }
  67.        iref, uref : stref; { original and uppercase terms }
  68.        phead : ppage;      { head of chain of page-refs   }
  69.        skip : boolean;     { phony node "M" starts a tree }
  70.        end;
  71.    pnode = ^node;
  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 ^strbuff; { 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 : ^strbuff;
  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 : ^strbuff;
  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 : ^strbuff;
  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 : ^strbuff;
  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 : ^node;
  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 : ^node;
  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 readnc : nchar;
  411.    { get one byte from the keyboard, bypassing the
  412.      usual pascal procedures and going straight to CP/M }
  413.    const bdos=5;
  414.      inchar=1;
  415.      asciicr=13;
  416.      asciilf=10;
  417.    type regs = record
  418.            a : 0..255;
  419.            bc,de,hl : integer
  420.            end;
  421.    var r : regs;
  422.    procedure call(var x:regs; addr:integer); external;
  423.    begin
  424.        r.bc := inchar;
  425.        call(r,bdos);
  426.        readnc := r.a
  427.    end;
  428.  
  429. procedure getterm(tree: pnode; var a:str; var cont: boolean);
  430.    { get a term from the user, with control keys used thus:
  431.        cr : end the term.
  432.        lf : end the term, begin a subterm of it.
  433.        esc: try to complete the term with the next (first)
  434.         matching term from the present tree-context.
  435.        del: cancel esc-completion, return to original entry.  }
  436.    var
  437.        c       : nchar;
  438.        j, oj   : strindex;
  439.        k       : strlength;
  440.        x,ua    : str;
  441.        quit    : boolean;
  442.        tw      : treewalk;
  443.        p       : pnode;
  444.  
  445.    procedure backup;
  446.        { backup the screen and the "a" string to the original
  447.        term that was entered. }
  448.        var qj  : strindex;
  449.        begin
  450.        for qj := j downto (oj+1) do
  451.          write(chr(asciibs),chr(asciiblank),chr(asciibs));
  452.        j := oj;
  453.        a.val[j] := nullch
  454.        end;
  455.  
  456.    procedure startscan;
  457.        { set up for an alphabetical scan over all terms that
  458.      are an initial match to user entry thus far.  Setscan
  459.      does most of the work. }
  460.        begin
  461.        stucase(a,ua); { for stepscan's benefit }
  462.        p := setscan(tree,tw,a);
  463.        if p<>nil then { phony node only if a.len=0 }
  464.            if p^.skip then p := treestep(tw);
  465.        if p<>nil then begin { this node has to be equal }
  466.            stget(p^.iref,x);
  467.            k := x.len+1
  468.        end
  469.        else k := 0
  470.        end;
  471.  
  472.    procedure stepscan;
  473.        { find the next match to the original string, leaving
  474.      its value in x, or k=0 if there is none.  }
  475.        begin
  476.        k := 0;
  477.        p := treestep(tw);
  478.        if p<>nil then
  479.            if p^.skip then p := treestep(tw);
  480.        if p<>nil then
  481.            if equal=sxcomp(ua,p^.uref) then begin
  482.            stget(p^.iref,x);
  483.            k := x.len+1
  484.            end
  485.        end;
  486.  
  487.    begin { the main Get Term procedure }
  488.        indent; write('term: ');
  489.        j := 1; oj := j;        { no data in the a-string      }
  490.        k := 0;               { no esc-scan working          }
  491.        quit := false;           { not finished yet (hardly!)   }
  492.        repeat
  493.        a.val[j] := nullch; { keep "a" a finished string   }
  494.        a.len := j-1;       { ..at all times           }
  495.        c := readnc;
  496.        case c of
  497.  
  498.        asciibs :           { destructive backspace          }
  499.            if j>1 then begin
  500.            write(chr(asciiblank),chr(asciibs));
  501.            j := j-1;
  502.            oj := j;    { the current scan is accepted }
  503.            k := 0;     { ..and no scan is underway    }
  504.            end;
  505.  
  506.        asciicr :           { normal completion          }
  507.            begin
  508.            write(chr(asciilf));
  509.            quit := true
  510.            end;
  511.  
  512.        asciilf :           { complete, move on to subterm }
  513.            begin
  514.            write(chr(asciicr));
  515.            quit := true
  516.            end;
  517.  
  518.        asciiesc :           { automatic scan for match     }
  519.            begin
  520.            backup;     { wipe rejected match if any   }
  521.            if k=0 then startscan else stepscan;
  522.            if k=0 then { no (further) match found     }
  523.                write(chr(asciibel))
  524.            else        { next (first?) match found    }
  525.                while j<k do begin
  526.                a.val[j] := x.val[j];
  527.                write(chr(a.val[j]));
  528.                j := j+1
  529.                end
  530.            end;
  531.  
  532.        asciidel :           { cancel search for match      }
  533.            begin
  534.            backup;
  535.            k := 0      { no active scan           }
  536.            end;
  537.  
  538.        else :           { ordinary (?) character       }
  539.            if (c<asciiblank) or (j=strmax) then
  540.            write(chr(asciibel))
  541.            else begin
  542.            a.val[j] := c;
  543.            j := j+1;
  544.            oj := j;    { the current scan has been   }
  545.            k := 0    { ..accepted and is over      }
  546.            end
  547.        end {case}
  548.        until quit;
  549.        cont := c=asciilf
  550.    end;
  551.  
  552. procedure getpage(var i: integer);
  553.    { read a page number into an integer.  If page numbers
  554.      are not simple integers, eg "3-17" and the like, this
  555.      routine would have to build a string. }
  556.    begin
  557.        indent;
  558.        write('page: ');
  559.        readln(i)
  560.    end;
  561.  
  562. procedure makepage(var p:ppage; i:integer);
  563.    { make a page record and install its address }
  564.    begin
  565.        new(p);
  566.        p^.next := nil;
  567.        p^.num  := i
  568.    end;
  569.  
  570. procedure addpage(np: pnode; pg: integer);
  571.    { add a page number to the chain off a node.  This is
  572.      a classic case of an algorithm that requires a 2-exit
  573.      loop; the scan of the chain has to stop when a higher
  574.      page number is found OR when the end of the chain is
  575.      reached.  It could be done with Repeat or While, but
  576.      it actually looks cleaner with Goto. }
  577.    label 99,101,102,103;
  578.    var p1, p2, p3: ppage;
  579.    begin
  580.        p1 := np^.phead;
  581.        if p1=nil then makepage(np^.phead,pg)
  582.        else  { some pages already noted, search chain }
  583.        if pg<p1^.num then begin
  584.            makepage(p2,pg); { this page less than all }
  585.            p2^.next := p1;
  586.            np^.phead := p2
  587.        end
  588.        else begin { this page goes somewhere in chain }
  589.        99: p2 := p1^.next;
  590.            if p2=nil then goto 101;
  591.            if pg<p2^.num then goto 102;
  592.            p1 := p2;
  593.            goto 99;
  594.        101: { p1^ last number in chain, pg is => it }
  595.            begin
  596.            if pg>p1^.num then
  597.                makepage(p1^.next,pg);
  598.            goto 103
  599.            end;
  600.        102: {p1^.num <= pg <p2^.num; pg goes between }
  601.            begin
  602.            if pg>p1^.num then begin
  603.                makepage(p3,pg);
  604.                p3^.next := p2;
  605.                p1^.next := p3
  606.            end
  607.            end;
  608.        103: ;
  609.        end
  610.    end;
  611.  
  612. procedure load(var atree:pnode);
  613.    { input control: load terms into a tree from the keyboard.
  614.      the code is recursive; if the user wants to do a subterm
  615.      this routine calls itself to load the sub-tree of the
  616.      superior term's node.  A page number of zero is a disaster
  617.      when we reload the saved tree, so one is converted to -1.}
  618.    var aterm : str;
  619.        anode : pnode;
  620.        apage : integer;
  621.        cont  : boolean;
  622.    begin
  623.        repeat
  624.        getterm(atree,aterm,cont);
  625.        if aterm.len>0 then begin
  626.            anode := insert(atree,aterm);
  627.            if not cont then begin
  628.            getpage(apage);
  629.            if apage=0 then apage := 32767;
  630.            addpage(anode,apage)
  631.            end
  632.            else begin { user hit lf, wants to recurse }
  633.            if anode^.subt=nil then
  634.                startree(anode^.subt);
  635.            indlevel := indlevel+1;
  636.            load(anode^.subt);
  637.            indlevel := indlevel-1
  638.            end
  639.        end;
  640.        until (aterm.len=0) or (indlevel>0)
  641.    end;
  642.  
  643. {-------------------------------------------------------------}
  644. {           routines for phase 2 -- output              }
  645. {-------------------------------------------------------------}
  646.  
  647. procedure filenode(np: pnode; var oc: nchar);
  648.    { write one node's contents, term + pages, to the output.
  649.      It is at this level that we insert a blank line on a break
  650.      in the sequence of main-term initial letters.  Once more,
  651.      a loop over an ordered chain is cleaner with Goto. }
  652.    label 99;
  653.    var a : str;
  654.        p : ppage;
  655.        i : 0..9;
  656.        j : strindex;
  657.        k1, k2 : integer;
  658.        ic : nchar;
  659.    begin
  660.        if not np^.skip then begin { ignore phony nodes }
  661.        stget(np^.iref,a);
  662.        ic := upcase(a.val[1]);
  663.        if (indlevel=0) and    { main-term initial change? }
  664.         (oc<>ic) then writeln(outfile);
  665.        oc := ic;
  666.        for i := 1 to indlevel do write(outfile,'   ');
  667.        for j := 1 to a.len do write(outfile,chr(a.val[j]));
  668.        p := np^.phead;
  669.        while p<>nil do begin
  670.            write(outfile,' ');
  671.            k1 := p^.num;
  672.            k2 := k1+1;
  673.         99:p := p^.next;   { elide sequential numbers     }
  674.            if p<>nil then
  675.            if p^.num=k2 then begin
  676.                k2 := k2+1;
  677.                goto 99
  678.            end;
  679.            write(outfile,k1:1); { write "17" or "17-19"   }
  680.            if (k1+1)<k2 then write(outfile,'-',k2-1:1);
  681.            if p<>nil then write(outfile,',');
  682.        end;
  683.        writeln(outfile);
  684.        end
  685.    end;
  686.  
  687. procedure filetree(intree: pnode);
  688.    { walk through a (sub-) tree and write each node }
  689.    var tree    : pnode;
  690.        tw      : treewalk;
  691.        oc      : nchar;
  692.    begin
  693.        oc := nullch;
  694.        initwalk(intree,tw);
  695.        tree := treestep(tw);
  696.        while tree<>nil do begin
  697.        filenode(tree,oc);
  698.        if tree^.subt<>nil then begin
  699.            indlevel := indlevel+1;
  700.            filetree(tree^.subt);
  701.            indlevel := indlevel-1
  702.        end;
  703.        tree := treestep(tw)
  704.        end
  705.    end;
  706.  
  707. procedure dump;
  708.    begin
  709.        rewrite('INDEXER.OUT',outfile);
  710.        filetree(maintree)
  711.    end;
  712.  
  713. {-------------------------------------------------------------}
  714. {       routines for phase 0 -- initialization          }
  715. {-------------------------------------------------------------}
  716.  
  717. procedure init;
  718.    { initialize the various mechanisms }
  719.    begin
  720.        indlevel := 0;
  721.        new (sbufptrs[1]);
  722.        sbufcnt := 1;
  723.        sbufptrs[1]^.free := sbufsize;
  724.        initerm.val[1] := ord('M');
  725.        initerm.val[2] := nullch;
  726.        initerm.len := 1;
  727.        startree(maintree);
  728.    end;
  729.  
  730. procedure loadall;
  731.    { if a saved-tree file INDEXER.TRE exists, load its values
  732.        into the tree.           }
  733.    var loadtree : file of nchar;
  734.        x   : str;
  735.        j   : strindex;
  736.        p   : pnode;
  737.        k   : integer;
  738.        k1,k2 : 0..255;
  739.  
  740.    procedure reload(t:pnode);
  741.        { reload one (sub-)tree from the saved-tree file }
  742.        { the recorded form of one node of a tree is:
  743.        termlength (1..strmax-1),
  744.        that many term bytes in reverse order,
  745.        page numbers as high byte, low byte,
  746.        page number of (zero,zero).
  747.        the file is a sequence of terms as above. a tree ends
  748.        with a byte of zero.  a sub-tree is introduced with a
  749.        byte of strmax.                          }
  750.  
  751.        begin {$r-  range checks off during byte i/o }
  752.        read(loadtree,j);
  753.        while j<>nullch do begin
  754.            x.len := j;
  755.            for j := j downto 1 do read(loadtree,x.val[j]);
  756.            x.val[x.len+1] := nullch;
  757.            p := insert(t,x);
  758.            repeat
  759.            read(loadtree,k1,k2);
  760.            k := (k1*256)+k2;
  761.            if k<>0 then addpage(p,k)
  762.            until k=0;
  763.            read(loadtree,j);
  764.            if j=strmax then begin { a sub-tree }
  765.            startree(p^.subt);
  766.            reload(p^.subt);
  767.            read(loadtree,j)
  768.            end
  769.        end
  770.        end; {$r+ }
  771.  
  772.    begin
  773.        reset('INDEXER.TRE',loadtree);
  774.        if not eof(loadtree) then reload(maintree)
  775.    end;
  776.  
  777. {-------------------------------------------------------------}
  778. {          routines for phase 3 -- termination          }
  779. {-------------------------------------------------------------}
  780.  
  781. procedure saveall;
  782.    { save the term-tree in the file INDEXER.TRE so it can
  783.        be reloaded for additions later, if need be. }
  784.    var savetree    : file of nchar;
  785.        x   : str;
  786.  
  787.    procedure unload(t:pnode);
  788.        { dump the contents of a (sub-) tree to disk in
  789.        "preorder," a sequence such that the exact layout
  790.        of the tree will be reconstructed if the tree is
  791.        reloaded from the file. }
  792.        label 99;
  793.        var j   : strindex;
  794.        p   : ppage;
  795.        k   : integer;
  796.        k1, k2 : nchar;
  797.        begin  {$r-  range checks off during byte i/o }
  798.        if t^.skip then goto 99; { dump not the phony node }
  799.        stget(t^.iref,x);
  800.        write(savetree,x.len);
  801.        for j:=x.len downto 1 do write(savetree,x.val[j]);
  802.        p := t^.phead;
  803.        while p<>nil do begin
  804.            k := p^.num;
  805.            k1 := k div 256; k2 := k mod 256;
  806.            write(savetree,k1,k2);
  807.            p := p^.next
  808.        end;
  809.        write(savetree,nullch,nullch); { flag end of pages }
  810.        if t^.subt<>nil then begin
  811.            write(savetree,strmax);{ flag start of subtree }
  812.            unload(t^.subt);
  813.            write(savetree,nullch) { flag end of subtree }
  814.        end;
  815.        99: if t^.lson<>nil then unload(t^.lson);
  816.        if t^.rson<>nil then unload(t^.rson);
  817.        end; {$r+ }
  818.  
  819.    begin
  820.        rewrite('INDEXER.TRE',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.