URI: 
       tCheckpoint: pull in mpm; merge pic from Taj's version of the world - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 5f1cf8e6fb130fd48d6f016d13baf5408b3181f8
   DIR parent c5561c23cf394806cbf6d70a96f2dc0253f93745
  HTML Author: wkj <devnull@localhost>
       Date:   Sun, 16 May 2004 07:56:41 +0000
       
       Checkpoint: pull in mpm; merge pic from Taj's version of the world
       
       Diffstat:
         A src/cmd/mpm/.cvsignore              |       1 +
         A src/cmd/mpm/README                  |     188 +++++++++++++++++++++++++++++++
         A src/cmd/mpm/misc.cc                 |      12 ++++++++++++
         A src/cmd/mpm/misc.h                  |      41 +++++++++++++++++++++++++++++++
         A src/cmd/mpm/mkfile                  |      24 ++++++++++++++++++++++++
         A src/cmd/mpm/page.cc                 |     612 +++++++++++++++++++++++++++++++
         A src/cmd/mpm/page.h                  |     119 +++++++++++++++++++++++++++++++
         A src/cmd/mpm/queue.cc                |     235 +++++++++++++++++++++++++++++++
         A src/cmd/mpm/range.cc                |     613 +++++++++++++++++++++++++++++++
         A src/cmd/mpm/range.h                 |     334 +++++++++++++++++++++++++++++++
         A src/cmd/mpm/slug.cc                 |     603 +++++++++++++++++++++++++++++++
         A src/cmd/mpm/slug.h                  |      74 +++++++++++++++++++++++++++++++
         A src/cmd/mpm/tmac.pm                 |     961 +++++++++++++++++++++++++++++++
         M src/cmd/pic/arcgen.c                |      13 ++++++++-----
         M src/cmd/pic/circgen.c               |       2 ++
         M src/cmd/pic/input.c                 |      21 ++++++++++++++-------
         M src/cmd/pic/main.c                  |      43 ++++++++++++++++---------------
         M src/cmd/pic/misc.c                  |      13 +++++++++----
         M src/cmd/pic/picl.lx                 |       2 +-
         M src/cmd/pic/print.c                 |       4 ++++
         M src/cmd/pic/symtab.c                |       2 +-
       
       21 files changed, 3878 insertions(+), 39 deletions(-)
       ---
   DIR diff --git a/src/cmd/mpm/.cvsignore b/src/cmd/mpm/.cvsignore
       t@@ -0,0 +1 @@
       +pm
   DIR diff --git a/src/cmd/mpm/README b/src/cmd/mpm/README
       t@@ -0,0 +1,188 @@
       +An experiment in page makeup for troff output...
       +
       +-mpm is a version of standard -ms that causes extra
       +information for vertical justification and figure
       +placement to be included in troff output.  Commands that
       +have been augmented to provide paddable space are
       +
       +        .SH and .NH
       +        .PP and .LP        no space if \n(PD is 0; normally .nr PD 0.3v; leave at least 1u
       +        .IP and .QP        also
       +        .EQ and .EN
       +        .TS and .TE        no space if \n(TS is 0; normally .nr TS 0.5v
       +        .PS and .PE
       +        .P1 and .P2        display programs in CW font
       +        .DS and .DE
       +        .QS and .QE
       +
       +Other commands, registers, strings, etc.:
       +
       +        .SP n                explicit paddable space, just like .sp n.
       +                        generally you should ALWAYS use .SP instead of .sp.
       +                        if you need exactly a given vertical space, you can say
       +                                .SP 3i exactly
       +                        this space won't be padded.
       +        .Tm words        prints "words" and the output page number on stderr
       +                        sorry about the spelling; -ms pre-empted .TM
       +        .NE n                like .ne.  note: does not cause a break
       +
       +                        Others may be added as the need arises.
       +
       +        .nr FO n        Set the page length.  This value is the bottom of
       +                        the text on the page; a bottom title may lie below.
       +                        default is 10i (== 10 inches).
       +        %o, %e                are strings containing odd and even page titles
       +        %#                is the current page number (often useless)
       +        .PT                is a macro invoked at the top of each "page";
       +                        it will normally use %e, %o and %#.  There is also
       +                        a .BT for page bottoms if desired.
       +        .BP                force a page break
       +        .FL                force all waiting figures out before any more running text
       +        .1C, .2C        multiple columns;  number registers CW and GW set
       +                        the column and gutter width if you don't like the default.
       +                        absent a .FC command, all two-column contents collect
       +                        together on the page
       +        .FC                freeze current two-column contents and start afresh.
       +                        necessary if you want to switch between 1 and 2 column
       +                        text and keep the relative order among them.
       +
       +Usage is some variant of
       +
       +        ... | troff -mpm
       +
       +/usr/lib/tmac/pm is the page-justifier itself;  it is called automatically
       +by the -mpm macro package.  If you are installing this yourself, you will
       +have to edit the 2nd line of tmac.pm to arrange that pm is called directly
       +from troff.
       +
       +There are several lines in tmac.pm that say
       +        .so /n/coma/usr/bwk/...
       +You should delete these;  they are placeholders for some experiments.
       +
       +If you use -mm, you are more or less out of luck, although we will be
       +happy to provide a crude and incomplete program that purports to convert
       +-mm to -ms.  It may suggest what you need but it won't do the job.
       +
       +To compile pm, you need a C++ compiler, preferably release 2.0 or later.
       +Put the .c and .h files in a directory, and type
       +        make
       +This process may well fail.  The usual cause is that different systems
       +put function declarations in different header files, and C++ insists that
       +all functions be properly declared.  You can almost always get through this
       +part by adding function declarations.  The most likely offender is malloc;
       +a line like
       +        extern char *malloc(int);
       +near the top of slug.c will solve this one.
       +
       +
       +Bugs, etc.:
       +
       +        not all -ms commands have been decorated;  in particular,
       +        the rich variety of document types (TM, CSTR, etc.,) is not
       +        really supported.
       +
       +        there are problems with funny first pages and troff input
       +        that moves back up the page.
       +
       +        multiple columns:  only .2C is available.  The program does not check
       +        whether something is wide or narrow:  user has responsibility to mark
       +        which with .1C or .2C.
       +
       +        headings are a bit tricky if you want things like
       +        running titles that include the current section title.
       +        normally a two-pass procedure using .Tm is needed.
       +        
       +        It's a pain to force a blank vertical space of specified height.
       +        Try this:
       +                .de x
       +                \v'\\$1'\0\h'-\w'\0'u'\c
       +                ..
       +                .x 2.5i
       +
       +
       +If you want to roll your own, the following components are
       +included in pm's "command language".  They are inserted in
       +the troff output in the form of "x X ..." commands, which
       +are created either by \X'...' or by the .X macro in -mpm.
       +Look at how they are used in /usr/lib/tmac/tmac.pm for examples.
       +
       +
       +BS n        breakable stream        n = min # lines that must appear on page
       +                                use:  PP, LP, IP, ...
       +
       +US        unbreakable stream        use:  KS/KE, DS/DE, TS/TE, EQ/EN, PS/PE, etc.
       +
       +BF v        breakable float                v = preferred vertical location of box center
       +                                use:  FS/FE
       +                                use two successive BF's to give two preferences
       +
       +UF v        unbreakable float        v = preferred vertical location of box center
       +                                use:  KF/KE
       +                                use two successive UF's to give two preferences
       +
       +PT        page title                use:  user has absolute control between PT and END
       +                                no SP's or other pm commands inside are processed
       +
       +BT        bottom title                use:  user has absolute control between BT and END
       +
       +END        end                        end a US, BF, UF, PT, or BT
       +                                all constructs nest, but a float within another float
       +                                or a US block will not float within or outside the block
       +
       +NE n        need                        break page if a VBOX of height n would not fit on page
       +                                use:  .NE n
       +
       +SP n        space                        paddable space of n
       +                                use:  .SP n
       +
       +PARM NP v                        top of pm text at v
       +        new page
       +
       +PARM FO v                        bottom of pm text at v
       +        footer                        length of text on page = FO-NP
       +
       +PARM PL v                        physical page ends at v
       +        page length                default = FO + NP
       +
       +PARM MF x                        tolerance to prevent padding
       +        minimum fullness        default = 0.9
       +
       +PARM CT x                        tolerance for two-column operation
       +        column tolerance        default = 0.5
       +
       +PARM DBG x                        debugging flag
       +
       +TM str        message                        .Tm words prints <pageno> <tab> <words> on stderr
       +
       +MC n o        multiple column                n columns, offset o.
       +                                Only 1 and 2 columns will work.
       +
       +CMD BP        break page                force page break
       +
       +CMD FL        flush                        force all queued figures out before any more
       +                                stream material is output
       +
       +CMD FC        freeze columns                force out current two-column contents;
       +                                start a fresh one
       +
       +Something like this will probably have to be added:
       +
       +NC        new column                HARD!
       +
       +Known botches in the existing implementation of pm:
       +
       +If a footnote is split across two pages, any associated separator line
       +will not be copied.  If there are multiple footnotes on one page, there
       +will be multiple separators too.  -mpm's .FS macro does not provide a
       +separator.  If you want a separator line, put it in explicitly with
       +a call to the .FA macro.
       +
       +There are not enough settable parameters;  in particular, the
       +way to control the height is a botch.
       +
       +
       +Historical note:  There is a simpler version of pm and -mpm
       +called pj and -mpj that only does vertical justification of
       +pages that have already been laid out by conventional means.
       +This simpler version may be adequate, but it is no longer
       +supported and memory of how it works is growing dim.
   DIR diff --git a/src/cmd/mpm/misc.cc b/src/cmd/mpm/misc.cc
       t@@ -0,0 +1,12 @@
       +#include        "misc.h"
       +
       +char        errbuf[200];
       +char        *progname;
       +int        wantwarn = 0;
       +
       +int        dbg        = 0;
       +// dbg = 1 : dump slugs
       +// dbg = 2 : dump ranges
       +// dbg = 4 : report function entry
       +// dbg = 8 : follow queue progress
       +// dbg = 16: follow page fill progress
   DIR diff --git a/src/cmd/mpm/misc.h b/src/cmd/mpm/misc.h
       t@@ -0,0 +1,41 @@
       +#include        <stdio.h>
       +#include        <stdlib.h>
       +#include        <math.h>
       +#include        <ctype.h>
       +#include        <string.h>
       +
       +// XXX: Apparently necessary for g++
       +#define        typename tyname
       +
       +extern char        errbuf[];
       +extern char        *progname;
       +extern int        linenum;
       +extern int        wantwarn;
       +
       +// #define        ERROR        fflush(stdout), fprintf(stderr, "%s: ", progname), fprintf(stderr,
       +// #define        FATAL        ), exit(1)
       +// #define        WARNING        )
       +
       +#define        ERROR        fprintf(stdout, "\n#MESSAGE TO USER:  "), sprintf(errbuf,
       +#define        FATAL        ), fputs(errbuf, stdout), \
       +                fprintf(stderr, "%s: ", progname), \
       +                fputs(errbuf, stderr), \
       +                fflush(stdout), \
       +                exit(1)
       +#define        WARNING        ), fputs(errbuf, stdout), \
       +                wantwarn ? \
       +                        fprintf(stderr, "%s: ", progname), \
       +                        fputs(errbuf, stderr) : 0, \
       +                fflush(stdout)
       +
       +#define        eq(s,t)        (strcmp(s,t) == 0)
       +
       +inline int        max(int x, int y)        { return x > y ? x : y; }
       +inline int        min(int x, int y)        { return x > y ? y : x; }
       +inline int        abs(int x)                { return (x >= 0) ? x : -x; }
       +
       +extern int        dbg;
       +
       +extern int        pn, userpn;                // actual and user-defined page numbers
       +extern int        pagetop, pagebot;        // printing margins
       +extern int        physbot;                // physical bottom of the page
   DIR diff --git a/src/cmd/mpm/mkfile b/src/cmd/mpm/mkfile
       t@@ -0,0 +1,24 @@
       +</$objtype/mkfile
       +
       +TARG=aux/pm
       +OFILES=misc.$O\
       +        slug.$O\
       +        range.$O\
       +        queue.$O\
       +        page.$O\
       +
       +HFILES=misc.h\
       +
       +BIN=/$objtype/bin
       +</sys/src/cmd/mkone
       +CC=c++/$CC
       +LD=c++/$LD
       +CFLAGS=
       +
       +slug.$O:        slug.h
       +range.$O:        range.h slug.h
       +queue.$O:        page.h range.h slug.h
       +page.$O:        page.h range.h slug.h
       +
       +test:V:        $O.out
       +        tryout $O.out
   DIR diff --git a/src/cmd/mpm/page.cc b/src/cmd/mpm/page.cc
       t@@ -0,0 +1,612 @@
       +#include        "misc.h"
       +#include        "slug.h"
       +#include        "range.h"
       +#include        "page.h"
       +
       +const int        MAXRANGES        = 1000;
       +static range *ptemp[MAXRANGES];                // for movefloats()
       +
       +static void swapright(int n)                // used by movefloats()
       +{
       +        range *t = ptemp[n];
       +        ptemp[n] = ptemp[n+1];
       +        ptemp[n+1] = t;
       +        ptemp[n]->setaccum( ptemp[n+1]->accum() -
       +                            ptemp[n+1]->rawht() + ptemp[n]->rawht() );
       +        ptemp[n+1]->setaccum( ptemp[n]->accum() + ptemp[n+1]->rawht() );
       +}
       +
       +// Figure out the goal position for each floating range on scratch,
       +// and move it past stream ranges until it's as close to its goal as possible.
       +static void movefloats(stream *scratch, double scale)
       +{
       +        int nranges, i;
       +        const int Huge = 100000;
       +
       +        for (nranges = 0; scratch->more(); scratch->advance())
       +                ptemp[nranges++] = scratch->current();
       +        scratch->freeall();
       +        ufrange rtemp;
       +        ptemp[nranges] = &rtemp;
       +        rtemp.setgoal(Huge);
       +        int accumV = 0;                                // compute accum values and
       +        for (i = 0; i < nranges; i++) {                // pick closest goal for floats
       +                ptemp[i]->pickgoal(accumV, scale);
       +                ptemp[i]->setaccum(accumV += ptemp[i]->rawht());
       +        }
       +        int j;                                        // index for inner loop below:
       +        for (i = nranges; --i >= 0; )                // stably sort floats to bottom
       +                for (j = i; j < nranges; j++)
       +                        if (ptemp[j]->goal() > ptemp[j+1]->goal())
       +                                swapright(j);
       +                        else
       +                                break;
       +        if (dbg & 16)
       +                printf("#movefloats:  before floating, from bottom:\n");
       +        for (i = nranges; --i >= 0; ) {                // find topmost float
       +                if (ptemp[i]->goal() == NOGOAL)
       +                        break;
       +                if (dbg & 16)
       +                        printf("# serialno %d goal %d height %d\n",
       +                                ptemp[i]->serialno(), ptemp[i]->goal(),
       +                                ptemp[i]->rawht());
       +        }                                        // i+1 is topmost float
       +        for (i++ ; i < nranges; i++)                // move each float up the page
       +                for (j = i; j > 0; j--)                // as long as closer to its goal
       +                        if (ptemp[j]->goal()
       +                          <= ptemp[j-1]->accum() + ptemp[j]->rawht()/2
       +                          && ptemp[j-1]->goal() == NOGOAL)
       +                                swapright(j-1);
       +                        else
       +                                break;
       +        if (ptemp[nranges] != &rtemp)
       +                ERROR "goal sentinel has disappeared from movefloats" FATAL;
       +        for (i = 0; i < nranges; i++)                // copy sorted list back
       +                scratch->append(ptemp[i]);
       +}
       +
       +// Traverse the leaves of a tree of ranges, filtering out only SP and VBOX.
       +static range *filter(generator *g)
       +{
       +        range *r;
       +        while ((r = g->next()) != 0)
       +                if (r->isvbox() || r->issp())
       +                        break;
       +        return r;
       +}
       +
       +// Zero out leading and trailing spaces; coalesce adjacent SP's.
       +static void trimspace(stream *scratch)
       +{
       +        generator g;
       +        range *r, *prevr = 0;
       +
       +        for (g = scratch; (r = filter(&g)) != 0 && r->issp(); prevr = r)
       +                r->setheight(0);                // zap leading SP
       +        for ( ; (r = filter(&g)) != 0; prevr = r)
       +                if (r->issp())
       +                        if (prevr && prevr->issp()) {
       +                                                // coalesce adjacent SPs
       +                                r->setheight(max(r->rawht(), prevr->height()));
       +                                prevr->setheight(0);
       +                        } else                        // a VBOX intervened
       +                                r->setheight(r->rawht());
       +        if (prevr && prevr->issp())                // zap *all* trailing space
       +                prevr->setheight(0);                // (since it all coalesced
       +                                                // into the last one)
       +}
       +
       +// Pad the non-zero SP's in scratch so the total height is wantht.
       +// Note that the SP values in scratch are not the raw values, and
       +// indeed may already have been padded.
       +static void justify(stream *scratch, int wantht)
       +{
       +        range *r;
       +        int nsp = 0, hsp = 0;
       +
       +        int adjht = scratch->height();
       +                                        // Find all the spaces.
       +        generator g;
       +        for (g = scratch; (r = g.next()) != 0; )
       +                if (r->issp() && r->height() > 0) {
       +                        nsp++;
       +                        hsp += r->height();
       +                }
       +        int excess = wantht - adjht;
       +        if (excess < 0)
       +                ERROR "something on page %d is oversize by %d\n",
       +                        userpn, -excess WARNING;
       +        if (dbg & 16)
       +                printf("# justify %d: excess %d nsp %d hsp %d adjht %d\n",
       +                        userpn, excess, nsp, hsp, adjht);
       +        if (excess <= 0 || nsp == 0)
       +                return;
       +                                        // Redistribute the excess space.
       +        for (g = scratch; (r = g.next()) != 0; )
       +                if (r->issp() && r->height() > 0) {
       +                        int delta = (int) ((float)(r->height()*excess)/hsp + 0.5);
       +                        if (dbg & 16)
       +                                printf("# pad space %d by %d: hsp %d excess %d\n",
       +                                        r->height(), delta, hsp, excess);
       +                        r->setheight(r->height() + delta);
       +                }
       +}
       +
       +// If r were added to s, would the height of the composed result be at most maxht?
       +int wouldfit(range *r, stream *s, int maxht)
       +{
       +        if (r->rawht() + s->rawht() <= maxht)
       +                return 1;                // the conservative test succeeded
       +        stream scratch;                        // local playground for costly test
       +        for (stream cd = *s; cd.more(); cd.advance())
       +                scratch.append(cd.current());
       +        scratch.append(r);
       +        movefloats(&scratch, ((double) scratch.rawht())/maxht);
       +        trimspace(&scratch);
       +        int retval = scratch.height() <= maxht;
       +        scratch.freeall();
       +        return retval;
       +}
       +
       +// If s1 were added to s, would the height of the composed result be at most maxht?
       +// The computational structure is similar to that above.
       +int wouldfit(stream *s1, stream *s, int maxht)
       +{
       +        if (s1->rawht() + s->rawht() <= maxht)
       +                return 1;
       +        stream scratch, cd;
       +        for (cd = *s; cd.more(); cd.advance())
       +                scratch.append(cd.current());
       +        for (cd = *s1; cd.more(); cd.advance())
       +                scratch.append(cd.current());
       +        movefloats(&scratch, ((double) scratch.rawht())/maxht);
       +        trimspace(&scratch);
       +        int retval = scratch.height() <= maxht;
       +        scratch.freeall();
       +        return retval;
       +}
       +
       +// All of stream *s is destined for one column or the other; which is it to be?
       +void multicol::choosecol(stream *s, int goalht)
       +{
       +        stream *dest;
       +        if (!leftblocked && wouldfit(s, &(column[0]), goalht))
       +                dest = &(column[0]);
       +        else {
       +                dest = &(column[1]);
       +                if (!s->current()->floatable())
       +                                        // a stream item is going into the right
       +                                        // column, so no more can go into the left.
       +                        leftblocked = 1;
       +        }
       +        for (stream cd = *s; cd.more(); cd.advance())
       +                dest->append(cd.current());
       +}
       +
       +double coltol = 0.5;
       +
       +// Try, very hard, to put everything in the multicol into two columns
       +// so that the total height is at most htavail.
       +void multicol::compose(int defonly)
       +{
       +        if (!nonempty()) {
       +                setheight(0);
       +                return;
       +        }
       +        scratch.freeall();        // fill scratch with everything destined
       +                                // for either column
       +        stream cd;
       +        for (cd = definite; cd.more(); cd.advance())
       +                scratch.append(cd.current());
       +        if (!defonly)
       +                for (cd = *(currpage->stage); cd.more(); cd.advance())
       +                        if (cd.current()->numcol() == 2)
       +                                scratch.append(cd.current());
       +        scratch.restoreall();                // in particular, floatables' goals
       +        int i;
       +        int rawht = scratch.rawht();
       +        int halfheight = (int)(coltol*rawht);
       +                                        // choose a goal height
       +        int maxht = defonly ? halfheight : htavail;
       +secondtry:
       +        for (i = 0; i < 2; i++)
       +                column[i].freeall();
       +        leftblocked = 0;
       +        cd = scratch;
       +        while (cd.more()) {
       +                queue ministage;        // for the minimally acceptable chunks
       +                ministage.freeall();        // that are to be added to either column
       +                while (cd.more() && !cd.current()->issentinel()) {
       +                        ministage.enqueue(cd.current());
       +                        cd.advance();
       +                }
       +                choosecol(&ministage, maxht);
       +                if (cd.more() && cd.current()->issentinel())
       +                        cd.advance();        // past sentinel
       +        }
       +        if (height() > htavail && maxht != htavail) {
       +                                        // We tried to balance the columns, but
       +                                        // the result was too tall.  Go back
       +                                        // and try again with the less ambitious
       +                                        // goal of fitting the space available.
       +                maxht = htavail;
       +                goto secondtry;
       +        }
       +        for (i = 0; i < 2; i++) {
       +                movefloats(&(column[i]), ((double) column[i].rawht())/currpage->pagesize);
       +                trimspace(&(column[i]));
       +        }
       +        if (dbg & 32) {
       +                printf("#multicol::compose: htavail %d maxht %d dv %d\n",
       +                        htavail, maxht, height());
       +                dump();
       +        }
       +        if (defonly)
       +                stretch(height());
       +}
       +
       +// A sequence of two-column ranges waits on the stage.
       +// So long as the page's skeleton hasn't changed--that is, the maximum height
       +// available to the two-column chunk is the same--we just use the columns that
       +// have been built up so far, and choose a column into which to put the stage.
       +// If the skeleton has changed, however, then we may need to make entirely
       +// new decisions about which column gets what, so we recompose the whole page.
       +void multicol::tryout()
       +{
       +        if (htavail == prevhtavail)
       +                choosecol(currpage->stage, htavail);
       +        else
       +                currpage->compose(DRAFT);
       +        prevhtavail = htavail;
       +}
       +
       +// Make both columns the same height.
       +// (Maybe this should also be governed by minfull,
       +// to prevent padding very underfull columns.)
       +void multicol::stretch(int wantht)
       +{
       +        if (wantht < height())
       +                ERROR "page %d: two-column chunk cannot shrink\n", userpn FATAL;
       +        for (int i = 0; i < 2; i++)
       +                justify(&(column[i]), wantht);
       +        if (dbg & 16)
       +                printf("#col hts: left %d right %d\n",
       +                        column[0].height(), column[1].height());
       +}
       +
       +// Report an upper bound on how tall the current two-column object is.
       +// The (possibly composed) heights of the two columns give a crude upper
       +// bound on the total height.  If the result is more than the height
       +// available for the two-column object, then the columns are each
       +// composed to give a better estimate of their heights.
       +int multicol::height()
       +{
       +        int retval = max(column[0].height(), column[1].height());
       +        if (retval < htavail)
       +                return retval;
       +        for (int i = 0; i < 2; i++) {
       +                movefloats(&(column[i]), ((double) column[i].height())/currpage->pagesize);
       +                trimspace(&(column[i]));
       +        }
       +        return max(column[0].height(), column[1].height());
       +}
       +
       +void multicol::dump()
       +{
       +        printf("####2COL dv %d\n", height());
       +        printf("# left column:\n");
       +        column[0].dump();
       +        printf("# right column:\n");
       +        column[1].dump();
       +}
       +
       +// From the head of queue qp, peel off a piece whose raw height is at most space.
       +int peeloff(stream *qp, int space)
       +{
       +        stream *s1 = qp->current()->children();
       +        if (!(s1 && s1->more() && s1->current()->height() <= space))
       +                                        // in other words, either qp's head is
       +                                        // not nested, or its first subrange
       +                return 0;                // is also too big, so we give up
       +        qp->split();
       +        s1 = qp->current()->children();
       +        stream *s2 = qp->next()->children();
       +        while (s2->more() && s2->current()->rawht() <= space) {
       +                s1->append(s2->current());
       +                space -= s2->current()->rawht();
       +                s2->advance();
       +        }
       +        return 1;
       +}
       +
       +// There are four possibilities for consecutive calls to tryout().
       +// If we're processing a sequence of single-column ranges, tryout()
       +// uses the original algorithm: (1) conservative test; (2) costly test;
       +// (3) split a breakable item.
       +// If we're processing a sequence of double-column ranges, tryout()
       +// defers to twocol->tryout(), which gradually builds up the contents
       +// of the two columns until they're as tall as they can be without
       +// exceeding twocol->htavail.
       +// If we're processing a sequence of single-column ranges and we
       +// get a double-column range, then we use compose() to build a
       +// skeleton page and set twocol->htavail, the maximum height that
       +// should be occupied by twocol.
       +// If we're processing a sequence of double-column ranges and we
       +// get a single-column range, then we should go back and squish
       +// the double-column chunk as short as possible before we see if
       +// we can fit the single-column range.
       +void page::tryout()
       +{
       +        if (!stage->more())
       +                ERROR "empty stage in page::tryout()\n" FATAL;
       +        int curnumcol = stage->current()->numcol();
       +        if (dbg & 32) {
       +                printf("#page::tryout(): ncol = %d, prevncol = %d; on stage:\n",
       +                        curnumcol, prevncol);
       +                stage->dump();
       +                printf("#END of stage contents\n");
       +        }
       +        switch(curnumcol) {
       +        default:
       +                ERROR "unexpected number of columns in tryout(): %d\n",
       +                        stage->current()->numcol() FATAL;
       +                break;
       +        case 1:
       +                if (prevncol == 2)
       +                        compose(FINAL);
       +                if (wouldfit(stage, &definite, pagesize - twocol->height()))
       +                        commit();
       +                else if (stage->current()->breakable() || blank()
       +                        && peeloff(stage,
       +                                pagesize - (definite.height() + twocol->height()))) {
       +                        // first add the peeled-off part that fits
       +                        adddef(stage->dequeue());
       +                        // then send the rest back for later
       +                        stage->current()->setbreaking();
       +                        welsh();
       +                } else if (blank()) {
       +                        stage->current()->rdump();
       +                        ERROR "A %s is too big to continue.\n",
       +                        stage->current()->typename() FATAL;
       +                } else
       +                        welsh();
       +                break;
       +        case 2:
       +                if (prevncol == 1)
       +                        compose(DRAFT);
       +                else
       +                        twocol->tryout();
       +                if (scratch.height() <= pagesize)
       +                        commit();
       +                else
       +                        welsh();
       +                break;
       +        }
       +        prevncol = curnumcol;
       +}
       +
       +// To compose the page, we (1) fill scratch with the stuff that's meant to
       +// go on the page; (2) compose scratch as best we can; (3) set the maximum
       +// height available to the two-column part of the page; (4) have the two-
       +// column part compose itself.
       +// In the computation of twocol->htavail, it does not matter that
       +// twocol->height() is merely an upper bound, because it is merely being
       +// subtracted out to give the exact total height of the single-column stuff.
       +void page::compose(int final)
       +{
       +        makescratch(final);
       +        int adjht = scratch.rawht();
       +        if (dbg & 16)
       +                printf("# page %d measure %d\n", userpn, adjht);
       +        movefloats(&scratch, ((double) adjht)/pagesize);
       +        trimspace(&scratch);
       +        twocol->htavail = pagesize - (scratch.height() - twocol->height());
       +        twocol->compose(final);
       +        adjht = scratch.height();
       +        if (dbg & 16)
       +                printf("# page %d measure %d after trim\n", userpn, adjht);
       +}
       +
       +// Fill the scratch area with ranges destined for the page.
       +// If defonly == 0, then add anything that's on stage--this is a trial run.
       +// If defonly != 0, use only what's definitely on the page.
       +void page::makescratch(int defonly)
       +{
       +        scratch.freeall();
       +        stream cd;
       +        for (cd = definite; cd.more(); cd.advance())
       +                scratch.append(cd.current());
       +        if (!defonly)
       +                for (cd = *stage; cd.more(); cd.advance())
       +                        if (cd.current()->numcol() == 1)
       +                                scratch.append(cd.current());
       +        if (twocol->nonempty())
       +                scratch.append(twocol);
       +}
       +
       +// Accept the current contents of the stage.
       +// If the stage contains two-column ranges, add a sentinel to indicate the end
       +// of a chunk of stage contents.
       +void page::commit()
       +{
       +        if (dbg & 4)
       +                printf("#entering page::commit()\n");
       +        int numcol = 0;
       +        while (stage->more()) {
       +                numcol = stage->current()->numcol();
       +                adddef(stage->dequeue());
       +        }
       +        if (numcol == 2)
       +                adddef(new sentrange);
       +}
       +
       +// Send the current contents of the stage back to its source.
       +void page::welsh()
       +{
       +        if (dbg & 4)
       +                printf("#entering page::welsh()\n");
       +        while (stage->more()) {
       +                range *r = stage->dequeue();
       +                r->enqueue(ANDBLOCK);
       +        }
       +}
       +
       +enum { USonly = 1 };
       +
       +// So long as anything is eligible to go onto the page, keep trying.
       +// Once nothing is eligible, compose and justify the page.
       +void page::fill()
       +{
       +        while (stage->prime())
       +                stage->pend();
       +        compose(FINAL);
       +        if (dbg & 16)
       +                scratch.dump();
       +        if (anymore()) {
       +                int adjht = scratch.height();
       +                if (adjht > minfull*pagesize) {
       +                        justify(&scratch, pagesize);
       +                        adjht = scratch.height();
       +                        int stretchamt = max(pagesize - adjht, 0);
       +                        twocol->stretch(twocol->height() + stretchamt);
       +                                        // in case the page's stretchability lies
       +                                        // entirely in its two-column part
       +                } else
       +                        ERROR "page %d only %.0f%% full; will not be adjusted\n",
       +                                userpn, 100*(double) adjht/pagesize WARNING;
       +        }
       +}
       +
       +void page::adddef(range *r)
       +{
       +        if (dbg & 4)
       +                printf("#entering page::adddef()\n");
       +        switch (r->numcol()) {
       +        case 1:        definite.append(r);
       +                break;
       +        case 2: twocol->definite.append(r);
       +                break;
       +        default: ERROR "%d-column range unexpected\n", r->numcol() FATAL;
       +        }
       +}
       +
       +int multicol::print(int cv, int col)
       +{
       +        if (col != 0)
       +                ERROR "multicolumn output must start in left column\n" FATAL;
       +        int curv = cv, maxv = cv;        // print left column
       +        for ( ; column[0].more(); column[0].advance()) {
       +                curv = column[0].current()->print(curv, 0);
       +                maxv = max(maxv, curv);
       +        }
       +        curv = cv;                        // print right column
       +        for ( ; column[1].more(); column[1].advance()) {
       +                curv = column[1].current()->print(curv, 1);
       +                maxv = max(maxv, curv);
       +        }
       +        return maxv;
       +}
       +
       +void page::print()
       +{
       +        static int tops = 1, bots = 1;
       +        if (!scratch.more()) {
       +                ERROR "## Here's what's left on squeue:\n" WARNING;
       +                squeue.dump();
       +                ERROR "## Here's what's left on bfqueue:\n" WARNING;
       +                bfqueue.dump();
       +                ERROR "## Here's what's left on ufqueue:\n" WARNING;
       +                ufqueue.dump();
       +                ERROR "page %d appears to be empty\n", userpn WARNING;
       +                fflush(stderr), fflush(stdout), exit(0);
       +                                        // something is very wrong if this happens
       +        }
       +        printf("p%d\n", userpn);        // print troff output page number
       +        if (ptlist.more()) {                // print page header
       +                ptlist.current()->print(0, 0);
       +                ptlist.advance();
       +        } else if (tops) {
       +                ERROR "ran out of page titles at %d\n", userpn WARNING;
       +                tops = 0;
       +        }
       +        int curv = 0;
       +        printf("V%d\n", curv = pagetop);// print page contents
       +        for ( ; scratch.more(); scratch.advance()) {
       +                curv = scratch.current()->print(curv, 0);
       +        }
       +        if (btlist.more()) {                // print page footer
       +                btlist.current()->print(0, 0);
       +                btlist.advance();
       +        } else if (bots) {
       +                ERROR "ran out of page bottoms at %d\n", userpn WARNING;
       +                bots = 0;
       +        }
       +        printf("V%d\n", physbot);        // finish troff output page
       +}
       +
       +int        pagetop        = 0;                // top printing margin
       +int        pagebot = 0;                // bottom printing margin
       +int        physbot = 0;                // physical bottom of page
       +
       +double minfull = 0.9;                // minimum fullness before padding
       +
       +int        pn        = 0;                // cardinal page number
       +int        userpn        = 0;                // page number derived from PT slugs
       +
       +static void makepage()
       +{
       +        page pg(pagebot - pagetop);
       +        ++pn;
       +        userpn = ptlist.more() ? ptlist.current()->pn() : pn;
       +        pg.fill();
       +        pg.print();
       +}
       +
       +static void conv(FILE *fp)
       +{
       +        startup(fp);                // read slugs, etc.
       +        while (anymore())
       +                makepage();
       +        lastrange->print(0, 0);        // trailer
       +        checkout();                // check that everything was printed
       +}
       +
       +int
       +main(int argc, char **argv)
       +{
       +        static FILE *fp = stdin;
       +        progname = argv[0];
       +        while (argc > 1 && argv[1][0] == '-') {
       +                switch (argv[1][1]) {
       +                case 'd':
       +                        dbg = atoi(&argv[1][2]);
       +                        if (dbg == 0)
       +                                dbg = ~0;
       +                        break;
       +                case 'm':
       +                        minfull = 0.01*atof(&argv[1][2]);
       +                        break;
       +                case 'c':
       +                        coltol = 0.01*atof(&argv[1][2]);
       +                        break;
       +                case 'w':
       +                        wantwarn = 1;
       +                        break;
       +                }
       +                argc--;
       +                argv++;
       +        }
       +        if (argc <= 1)
       +                conv(stdin);
       +        else
       +                while (--argc > 0) {
       +                        if (strcmp(*++argv, "-") == 0)
       +                                fp = stdin;
       +                        else if ((fp = fopen(*argv, "r")) == NULL)
       +                                ERROR "can't open %s\n", *argv FATAL;
       +                        conv(fp);
       +                        fclose(fp);
       +                }
       +        exit(0);
       +        return 0;        /* gcc */
       +}
   DIR diff --git a/src/cmd/mpm/page.h b/src/cmd/mpm/page.h
       t@@ -0,0 +1,119 @@
       +extern queue        squeue;                        // the three queues on which ranges reside
       +extern queue        bfqueue;
       +extern queue        ufqueue;
       +
       +extern double minfull;
       +
       +extern double coltol;
       +
       +int anymore();
       +
       +// The following is used in some calls to range::enqueue(int = 0).
       +#define ANDBLOCK 1
       +
       +class page;
       +
       +enum { DRAFT = 0, FINAL = 1 };
       +
       +// The mergestream currpage->stage serves as a staging area for page makeup:
       +// when primed, it contains a minimal acceptable chunk of input ranges.
       +// The page must either take or leave everything that's on stage.
       +class mergestream : public queue {
       +        page        *currpage;                // current page that's accepting stuff
       +  public:
       +        mergestream(page *cp)        { currpage = cp; unblock(); }
       +        void        unblock();
       +        int        prime();                // stage next legal chunk
       +        void        pend();                        // process pending chunk on stage
       +};
       +
       +// The multicol currpage->twocol is the two-column piece of the page to which
       +// two-column ranges are currently being added.
       +// The page sets htavail to indicate how tall it is allowed to become.
       +// All ranges on definite must be placed when the multicol is printed.
       +// Each of these definite ranges also resides on one of column[0] and [1],
       +// which represent the current best guess about how to divide definite
       +// between the two columns.
       +class multicol : public range {
       +        page        *currpage;                // current page that's accepting stuff
       +        stream        definite;                // definitely on page
       +        stream        scratch;                // for trial compositions
       +        stream        column[2];                // left (0) and right (1) columns
       +        int        leftblocked;                // OK to add to left column?
       +        int        htavail;                // max possible ht, set by page::tryout()
       +        int        prevhtavail;                // max 2-colht last time we added something
       +        friend        class page;
       +public:
       +        multicol(page *cp)        { currpage = cp;
       +                                leftblocked = 0;
       +                                htavail = 0;
       +                                prevhtavail = -1;
       +                                setgoal(NOGOAL); }
       +                                        // the two-column piece behaves as part
       +                                        // of the stream of single-column input.
       +        int        numcol()        { return 1; }
       +        int        nonempty()        { return definite.more(); }
       +        void        choosecol(range *, int);// add first arg to one or other column
       +        void        choosecol(stream*, int);// add *all ranges on first arg*
       +                                        // to one or other column
       +                                        // NOT the same as a mapcar of the
       +                                        // preceding function over the ranges
       +                                        // on the first argument!
       +        void        compose(int);                // divide into two columns
       +        void        tryout();                // decide which column gets stage contents
       +        void        stretch(int);                // justify both columns to given height
       +        int        print(int curv, int col);
       +        int        height();                // an upper bound on actual height
       +        int        rawht()                { return max(column[0].rawht(), column[1].rawht()); }
       +        void        reheight(int *cv, int *mv)
       +                                { *cv += height(); *mv = max(*mv, *cv); }
       +        void        dump();
       +        int        isvbox()        { return nonempty(); }        // during trimspace()
       +};
       +
       +// These sentinel ranges are used to separate the ranges on twocol::definite
       +// into the chunks in which they came from the staging area.
       +// Thus, they preserve the results of the computation that was done to prime
       +// page::stage.
       +class sentrange : public range {
       +  public:
       +        sentrange()                { }
       +        int        numcol()        { return 2; }
       +        int        issentinel()        { return 1; }
       +};
       +
       +class page {
       +        int        pagesize;                // allowed maximum height
       +        int        prevncol;                // was last item tried 1- or 2-column?
       +        int        vsince;                        // how many vboxes from "current" BS
       +                                        // (to avoid putting a single line on
       +                                        // a page with a very large floatable)
       +        stream        definite;                // definitely on page, in input order
       +        stream        scratch;                // playground in which to alter page
       +        void        cmdproc();                // process any of several commands
       +        void        parmproc();                // process any of several parameters
       +        void        tryout();                // see whether current stage contents fit
       +        void        compose(int);                // float and trim current page contents
       +        void        makescratch(int);        // fill scratch area
       +        void        commit();                // accept the items on stage
       +        void        welsh();                // reject the items on stage
       +        void        adddef(range *r);        // add to one of the definite queues
       +                                        // (definite or twocol->definite)
       +  public:
       +        mergestream *stage;
       +        friend        class mergestream;
       +        multicol *twocol;
       +        friend class multicol;
       +        page(int p)        { pagesize = p;
       +                        prevncol = 1;
       +                        vsince = 0;
       +                        stage = new mergestream(this);
       +                        twocol = new multicol(this); }
       +        ~page()        { definite.freeall(); scratch.freeall(); }
       +        void        fill();
       +        int        blank()        { return !definite.more() && !twocol->definite.more();}
       +        void        print();
       +};
       +
       +// functions in page.c
       +int main(int, char **);
   DIR diff --git a/src/cmd/mpm/queue.cc b/src/cmd/mpm/queue.cc
       t@@ -0,0 +1,235 @@
       +#include        "misc.h"
       +#include        "slug.h"
       +#include        "range.h"
       +#include        "page.h"
       +
       +queue        squeue;
       +queue        bfqueue;
       +queue        ufqueue;
       +
       +// We use the stream function current() to access a queue's head.
       +// Thus, queue member curr should always point to its first range.
       +void queue::check(char *whence)
       +{
       +        if (dbg & 8) {
       +                char *p;
       +                if (this == &squeue)
       +                        p = "squeue";
       +                else if (this == &bfqueue)
       +                        p = "bfqueue";
       +                else if (this == &ufqueue)
       +                        p = "ufqueue";
       +                else
       +                        p = "weird queue";
       +                printf("#checking %s\n", p);
       +        }
       +        if (first != curr)
       +                ERROR "check(%s): first != curr, line %d\n", whence, curr->rp->lineno() FATAL;
       +}
       +
       +// When ranges are told to enqueue themselves, they are being rejected from the
       +// stage back onto their original queues.
       +// They reset any parameters that may have been altered by staging or trial
       +// composition.
       +
       +void        range::enqueue(int block)
       +{
       +        squeue.enqueue(this);
       +        if (block)
       +                squeue.block();
       +}
       +
       +void        ufrange::enqueue(int block)
       +{
       +        restore();                        // both goal positions
       +        ufqueue.enqueue(this);
       +        if (block)
       +                ufqueue.block();
       +}
       +
       +void        bfrange::enqueue(int block)
       +{
       +        restore();                        // both goal positions
       +        bfqueue.enqueue(this);
       +        if (block)
       +                bfqueue.block();
       +}
       +
       +int anymore()
       +{
       +        return !(squeue.empty() && ufqueue.empty() && bfqueue.empty());
       +}
       +
       +void mergestream::unblock()
       +{
       +        squeue.unblock();
       +        bfqueue.unblock();
       +        ufqueue.unblock();
       +}
       +
       +// Fill the staging area with a minimal chunk of input ranges.
       +int mergestream::prime()
       +{
       +        if (dbg & 4)
       +                printf("#entering mergestream::prime()\n");
       +        if (!empty())
       +                return 1;
       +        int brkok = 1;                        // is it OK to break after the last
       +                                        // VBOX that was added to the stage?
       +        int needheight = -1;                // minimum acceptable height of the
       +                                        // chunk being constructed on stage
       +        // If the range at the head of any queue is breaking,
       +        // deal with it first.
       +        if (squeue.more() && squeue.current()->breaking())
       +                enqueue(squeue.dequeue());
       +        else if (bfqueue.more() && (bfqueue.current()->breaking() ||
       +                (bfqueue.serialno() < squeue.serialno())))
       +                enqueue(bfqueue.dequeue());
       +        else if (ufqueue.more() && (ufqueue.current()->breaking() ||
       +                (ufqueue.serialno() < squeue.serialno())))
       +                enqueue(ufqueue.dequeue());
       +        else while (squeue.more()) {
       +                // Fill the stage with enough ranges to be a valid chunk.
       +                range *r = squeue.dequeue();
       +                if (r->isvbox()) {        // VBOX
       +                        if (dbg & 16)
       +                                printf("#VBOX: !empty: %d; brkok: %d; vsince: %d\n",
       +                                        !empty(), brkok, currpage->vsince);
       +                        if (!empty()        // there's something there
       +                                && brkok
       +                                        // it's OK to break here
       +                                && currpage->vsince >= 2
       +                                        // enough stream has gone onto this page
       +                                && rawht() >= needheight
       +                                        // current need has been satisfied
       +                                ) {
       +                                        // the stage already contains enough
       +                                        // ranges, so this one can wait
       +                                r->enqueue();
       +                                break;
       +                        } else {
       +                                if (r->rawht() > 0) {
       +                                        ++currpage->vsince;
       +                                        brkok = r->brkafter();
       +                                }
       +                                enqueue(r);
       +                        }
       +                } else if (r->isnested() || r->issp()) {        // US, SP
       +                        if (!empty() && rawht() >= needheight) {
       +                                        // enough already, wait
       +                                r->enqueue();
       +                                break;
       +                        }
       +                        currpage->vsince = 0;
       +                        enqueue(r);
       +                        if (height() >= needheight)
       +                                break;
       +                } else if (r->isneed()) {        // NE
       +                        if (!empty() && rawht() >= needheight) {
       +                                        // not currently working on an unsatisfied NEed 
       +                                r->enqueue();
       +                                break;
       +                        }
       +                                        // deal with overlapping NEeds
       +                        needheight = rawht() + max(needheight - rawht(), r->needht());
       +                        enqueue(r);
       +                } else if (r->forceflush() == NO) {
       +                        enqueue(r);
       +                } else if (r->forceflush() == YES) {
       +                        currpage->vsince = 0;
       +                        if (!empty()) {
       +                                        // ready or not, r must wait
       +                                r->enqueue();
       +                                break;
       +                        }
       +                        enqueue(r);
       +                        break;
       +                } else
       +                        ERROR "unexpected  %s[%s] in prime(), line %d\n",
       +                                r->typename(), r->headstr(), r->lineno() FATAL;
       +        }
       +        return more();                        // 0 if nothing was staged
       +}
       +
       +void page::cmdproc()
       +{
       +        if (stage->next())
       +                ERROR "more than a single command on bsqueue\n" FATAL;
       +        switch (stage->current()->cmdtype()) {
       +        case FC:        // freeze the current 2-column range and start a new one
       +                adddef(stage->dequeue());
       +                twocol->compose(FINAL);
       +                adddef(twocol);
       +                twocol = new multicol(this);
       +                break;
       +        case BP:        // force a page break
       +                adddef(stage->dequeue());
       +                squeue.block();
       +                break;
       +        case FL:        // flush out all floatables that precede this range:
       +                        // no more stream input allowed until they're past
       +                if (stage->serialno() > ufqueue.serialno() ||
       +                        stage->serialno() > bfqueue.serialno()) {
       +                        range *r = stage->dequeue();
       +                        r->enqueue(ANDBLOCK);
       +                } else
       +                        adddef(stage->dequeue());
       +                break;
       +        default:
       +                stage->current()->dump();
       +                ERROR "unknown command\n" FATAL;
       +        }
       +}
       +
       +void page::parmproc()
       +{
       +        if (stage->next())
       +                ERROR "more than a single parameter on bsqueue\n" FATAL;
       +        switch (stage->current()->parmtype()) {
       +        case NP:        // page top margin
       +                if (blank())
       +                        pagetop = stage->current()->parm();
       +                pagesize = pagebot - pagetop;
       +                break;
       +        case FO:
       +                if (blank())
       +                        pagebot = stage->current()->parm();
       +                pagesize = pagebot - pagetop;
       +                break;
       +        case PL:
       +                if (blank())
       +                        physbot = stage->current()->parm();
       +                break;
       +        case MF:
       +                minfull = 0.01*stage->current()->parm();
       +                break;
       +        case CT:
       +                coltol = 0.01*stage->current()->parm();
       +                break;
       +        case WARN:
       +                wantwarn = stage->current()->parm();
       +                break;
       +        case DBG:
       +                dbg = stage->current()->parm();
       +                break;
       +        default:
       +                stage->current()->dump();
       +                ERROR "unknown parameter\n" FATAL;
       +        }
       +        adddef(stage->dequeue());
       +}
       +
       +// Process the contents of the staging area; a relic that used to do more.
       +void mergestream::pend()
       +{
       +        if (dbg & 4)
       +                printf("#entering mergestream::pend()\n");
       +        if (!more())
       +                return;
       +        if (current()->iscmd())
       +                currpage->cmdproc();
       +        else if (current()->isparm())
       +                currpage->parmproc();
       +        else
       +                currpage->tryout();
       +}
   DIR diff --git a/src/cmd/mpm/range.cc b/src/cmd/mpm/range.cc
       t@@ -0,0 +1,613 @@
       +#include        <math.h>
       +#include        "misc.h"
       +#include        "slug.h"
       +#include        "range.h"
       +
       +void sprange::reheight(int *cv, int *mv)
       +{
       +        if (*cv != *mv)
       +                ERROR "slug %d: an imbedded SP, line %d\n",
       +                        first->serialno(), first->lineno() WARNING;
       +        *cv += dv;
       +        *mv = max(*mv, *cv);
       +}
       +
       +void sprange::rerawht(int *cv, int *mv)
       +{
       +        *cv += rawht();
       +        *mv = max(*mv, *cv);
       +}
       +
       +void nestrange::restore()
       +{
       +        subrange->restoreall();
       +}
       +
       +void stream::freeall()        // not a destructor;  called explicitly
       +{
       +        strblk *p, *q;
       +        for (p = first; p; p = q) {
       +                q = p->next;
       +                delete p;
       +        }
       +        first = last = curr = 0;
       +}
       +
       +void stream::dump()
       +{
       +        for (stream s = *this; s.more(); s.advance())
       +                s.current()->dump();
       +}
       +
       +void stream::rdump()
       +{
       +        for (stream s = *this; s.more(); s.advance())
       +                s.current()->rdump();
       +}
       +
       +int stream::restoreall()
       +{
       +        for (stream s = *this; s.more(); s.advance())
       +                s.current()->restore();
       +        return measure(this);
       +}
       +
       +range *stream::append(range *r)
       +{
       +        if (last == 0)
       +                curr = first = last = new strblk;
       +        else {
       +                last->next = new strblk;
       +                last = last->next;
       +                if (curr == 0)
       +                        curr = last;
       +        }
       +        last->next = 0;
       +        return last->rp = r;
       +}
       +
       +void stream::split()        // duplicate current() range
       +{
       +        strblk *s2 = new strblk;
       +        range *r2 = curr->rp->clone();
       +        s2->rp = r2;
       +        s2->next = curr->next;
       +        if (last == curr)
       +                last = s2;
       +        curr->next = s2;
       +        curr->rp->killkids();                // children only in the 2nd one
       +        // r2->crosslink(r1);
       +}
       +
       +int stream::height()
       +{
       +        int h;
       +        stream s = *this;
       +        for (h = 0; s.more(); s.advance())
       +                h += s.current()->height();
       +        return h;
       +}
       +
       +int stream::rawht()
       +{
       +        int h;
       +        stream s = *this;
       +        for (h = 0; s.more(); s.advance())
       +                h += s.current()->rawht();
       +        return h;
       +}
       +
       +int measure(stream *sp)                // record high-water mark of stream
       +{                                // sets nested stream heights
       +        stream s = *sp;
       +        int curv, maxv;
       +        for (maxv = curv = 0; s.more(); s.advance())
       +                s.current()->reheight(&curv, &maxv);
       +        return maxv;
       +}
       +
       +int rawmeasure(stream *sp)
       +{
       +        stream s = *sp;
       +        int curv, maxv;
       +        for (maxv = curv = 0; s.more(); s.advance())
       +                s.current()->rerawht(&curv, &maxv);
       +        return maxv;
       +}
       +
       +void nestrange::rdump()
       +{
       +        dump();
       +        if (subrange)
       +                subrange->rdump();
       +}
       +
       +void nestrange::killkids()
       +{
       +        subrange = new stream;
       +}
       +
       +int nestrange::print(int curv, int col)
       +{
       +        int ocurv = curv;
       +        first->slugout(col);
       +        for (stream s = *subrange; s.more(); s.advance())
       +                curv = s.current()->print(curv, col);
       +        return ocurv + height();
       +}
       +
       +#define macroclone(rangetype) range *rangetype::clone() {\
       +        rangetype *t = new rangetype;\
       +        *t = *this;\
       +        return t; }
       +
       +macroclone(usrange);
       +macroclone(ufrange);
       +macroclone(bfrange);
       +
       +#undef macroclone
       +
       +#define macropickgoal(rangetype) void rangetype::pickgoal(int acv, double scale) {\
       +        if (scale > 1) {\
       +                goalV = (int)(scale*goalV);\
       +                goal2 = (int)(scale*goal2);\
       +        }\
       +        if (abs(acv - goalV) > abs(acv-goal2))\
       +                goalV = goal2; }
       +
       +macropickgoal(ufrange)
       +macropickgoal(bfrange)
       +
       +#undef macropickgoal
       +
       +range *generator::next()
       +{
       +        range *r;
       +        if (child) {
       +                if ((r = child->next()) != 0)
       +                        return r;
       +                delete child;
       +                child = 0;
       +        }
       +        if (!s.more())
       +                return 0;
       +        r = s.current();
       +        if (r->isnested())
       +                child = new generator(r->children());
       +        s.advance();
       +        return r;
       +}
       +
       +range *queue::enqueue(range *r)
       +{
       +        if (dbg & 8)
       +                printf("#entering queue::enqueue()\n");
       +        check("queue::enqueue");
       +        if (!last || last->rp->serialno() < r->serialno())        // common case
       +                return append(r);
       +        if (dbg & 8)
       +                printf("#queue::enqueue() pushing back\n");
       +        newguy = new strblk;
       +        newguy->rp = r;
       +        if (r->serialno() < first->rp->serialno()) {
       +                newguy->next = first;
       +                curr = first = newguy;
       +                return newguy->rp;
       +        }
       +        if (dbg & 8)
       +                printf("#queue::enqueue() searching down queue\n");
       +        for (curr = first;
       +                next() && next()->serialno() < r->serialno();
       +                curr = curr->next)
       +                ;
       +        newguy->next = curr->next;
       +        curr->next = newguy;
       +        curr = first;                        // restore important queue condition
       +        return newguy->rp;
       +}
       +
       +range *queue::dequeue()
       +{
       +        if (dbg & 8)
       +                printf("#entering queue::dequeue()\n");
       +        check("queue::dequeue");
       +        curr = first->next;
       +        range *retval = first->rp;
       +        delete first;
       +        first = curr;
       +        if (!curr)
       +                last = 0;
       +        return retval;
       +}
       +
       +// ================================================================================
       +
       +//        functions that munge the troff output stored in slugs[]
       +
       +// ================================================================================
       +
       +static void doprefix(FILE *fp) // copy 1st "x" commands to output
       +{
       +        int c;
       +
       +        while ((c = getc(fp)) != EOF) {
       +                if (c != 'x') {
       +                        ungetc(c, fp);
       +                        break;
       +                }
       +                putchar(c);
       +                do {
       +                        putchar(c = getc(fp));
       +                } while (c != '\n');
       +                linenum++;
       +        }
       +//        printf("x font 1 R\n");        // horrible kludge: ensure a font for first f1 command 
       +}
       +
       +#define        DELTASLUGS        15000
       +
       +static slug        *slugs = 0;
       +static int        nslugs = 0;        // slugs has nslugs slots
       +static slug        *slugp = 0;        // next free slug in slugs
       +
       +static void readslugs(FILE *fp)
       +{
       +        if ((slugs = (slug *) malloc((nslugs = DELTASLUGS)*sizeof(slug))) == NULL)
       +                ERROR "no room for %d-slug array\n", nslugs FATAL;
       +        slugp = slugs;
       +        for (slugp = slugs; ; slugp++) {
       +                if (slugp >= slugs+nslugs-2) {
       +                        int where = slugp - slugs;
       +                        if ((slugs = (slug *) realloc((char *) slugs, (nslugs += DELTASLUGS)*sizeof(slug))) == NULL)
       +                                ERROR "no room for %d slugs\n", nslugs FATAL;
       +                        ERROR "now slug array can hold %d slugs\n", nslugs WARNING;
       +                        slugp = slugs + where;
       +                }
       +                *slugp = getslug(fp);
       +                if (slugp->type == EOF)
       +                        break;
       +        }
       +        *++slugp = eofslug();
       +        printf("# %d slugs\n", slugp-slugs);
       +}
       +
       +static slug *findend(slug *sp)
       +{
       +        slug *p;
       +        for (p = sp; p->type == sp->type; p++)        // skip runs
       +                ;                                // espec UF UF UF 
       +        for ( ; p < slugp; p++)
       +                switch (p->type) {
       +                case US:
       +                case UF:
       +                case BF:
       +                case PT:
       +                case BT:
       +                        p = findend(p);
       +                        break;
       +                case END:
       +                        return p;
       +                }
       +        ERROR "walked past EOF in findend looking for %d (%s), line %d\n",
       +                sp->type, sp->typename(), sp->lineno() FATAL;
       +        return sp;
       +}
       +
       +static int markp(int i, int n, int parm)
       +{        // should VBOX i of n be marked to brevent breaking after it?
       +        if (i >= n-1)
       +                return 0;
       +        return i <= parm-2 || i >= n-parm;
       +}
       +
       +static void markbreak(slug *p)
       +{
       +        // Mark impermissible breakpoints in BS's.
       +        // The parm field of a VBOX is >0 if we shouldn't break after it.
       +        int parm;                // how many lines must stay on page
       +        int goahead = 1;        // true until we see the next BS
       +        int nowmark = 0;        // true when we should be marking
       +        int n = 0;
       +        while (p->type == BS)
       +                parm = p++->parm;        // latest BS parm applies
       +        slug *op = p;
       +        while (goahead) {
       +                switch (p->type) {
       +                case VBOX:                // count VBOXes so second pass knows
       +                        if (p->dv > 0)        // knows how far to end of BS
       +                                n++;
       +                        break;
       +                case US:                // mark around EQ/EN, etc.
       +                        nowmark = 1;
       +                        p = findend(p);
       +                        break;
       +                case UF:                // but not around floats, PTs, and BTs
       +                case BF:
       +                case PT:
       +                case BT:
       +                        p = findend(p);
       +                        break;
       +                case SP:                // naked SP:  probable macro botch
       +                        nowmark = 1;        // mark around it anyhow
       +                        break;
       +                case BS:                // beginning of next paragraph
       +                case END:                // probable macro botch
       +                case EOF:
       +                        goahead = 0;        // stop work after marking
       +                        nowmark = 1;
       +                default:
       +                        break;
       +                }
       +                p++;
       +                if (nowmark) {
       +                        int i = 0;        // VBOX counter for second pass
       +                        while (op < p) {
       +                                switch (op->type) {
       +                                case VBOX:
       +                                        if (op->dv > 0)
       +                                                op->parm = markp(i, n, parm);
       +                                        i++;
       +                                        break;
       +                                case US:        // caused second pass to begin
       +                                case SP:
       +                                case BS:
       +                                case END:
       +                                case EOF:
       +                                        op = p;
       +                                        break;
       +                                case UF:        // skip on this pass too
       +                                case BF:
       +                                case PT:
       +                                case BT:
       +                                        op = findend(op);
       +                                        break;
       +                                default:
       +                                        break;
       +                                }
       +                        op++;
       +                        }
       +                        if (i != n)
       +                                ERROR "markbreak failed : i %d n %d\n",
       +                                        i, n WARNING;
       +                        op = p;
       +                        nowmark = n = 0;
       +                }
       +        }
       +}
       +
       +static void fixslugs()                // adjust bases and dv's, set parameters, etc.
       +{
       +        slug *p, *prevV = 0;
       +        for (p = slugs; p < slugp; p++) {
       +                if (p->type == VBOX) {
       +                        prevV = p;
       +                        continue;
       +                }
       +                if (p->base != 0) {
       +                        ERROR "%s slug (type %d) has base = %d, line %d\n",
       +                                p->typename(), p->type, p->base, p->lineno() WARNING;
       +                }
       +                if ((p->type == SP) || (p->type == NE))
       +                        continue;
       +                if (p->type == PAGE)
       +                        prevV = 0;
       +                if (p->dv != 0)
       +                        if (prevV) {
       +                                prevV->base = max(prevV->base, p->dv);
       +                                p->dv = 0;
       +                        } else {
       +                                ERROR "%s slug (type %d) has dv = %d, line %d\n",
       +                                        p->typename(), p->type, p->dv, p->lineno() WARNING;
       +                        }
       +        }
       +        prevV = 0;
       +        int firstNP = 0, firstFO = 0, firstPL = 0;
       +        for (p = slugs; p < slugp; p++) {
       +                switch (p->type) {
       +                // adjust the dv in a sequence of VBOXes
       +                // by subtracting from each the base of the preceding VBOX
       +                case VBOX:
       +                        if (prevV)
       +                                p->dv -= prevV->base;
       +                        prevV = p;
       +                        break;
       +                case SP:
       +                        p->dv = max(p->dv, 0);
       +                        break;
       +                case PAGE:
       +                        p->neutralize();
       +                        prevV = 0;
       +                        break;
       +                // record only first "declarations" of Page Top and bottom (FO);
       +                case PARM:
       +                        switch (p->parm) {
       +                        case NP:
       +                                if (firstNP++ == 0)
       +                                        pagetop = p->parm2;
       +                                p->neutralize();
       +                                break;
       +                        case FO:
       +                                if (firstFO++ == 0)
       +                                        pagebot = p->parm2;
       +                                p->neutralize();
       +                                break;
       +                        case PL:
       +                                if (firstPL++ == 0)
       +                                        physbot = p->parm2;
       +                                p->neutralize();
       +                                break;
       +                        }
       +                        break;
       +                // things that begin groups; not US, which should nest properly
       +                case UF:
       +                case BF:
       +                        while ((p+1)->type == p->type) {
       +                                                        // join adjacent identical
       +                                (p+1)->parm2 = p->parm;        // parm is latest
       +                                                        // parm2 is previous
       +                                p->neutralize();        // so it's not seen later
       +                                p++;
       +                        }
       +                        break;
       +                // none of the above
       +                case US:
       +                case PT:
       +                case BT:
       +                case BS:
       +                case END:
       +                case TM:
       +                case COORD:
       +                case NE:
       +                case MC:
       +                case CMD:
       +                case EOF:
       +                        break;
       +                default:
       +                        ERROR "Unknown slug type %d in fixslugs, line %d\n",
       +                                p->type, p->lineno() WARNING;
       +                        break;
       +                }
       +        }
       +        int pagesize = pagebot - pagetop;
       +        if (pagesize == 0)
       +                ERROR "Page dimensions not declared\n" FATAL;
       +        if (physbot == 0)
       +                physbot = pagebot + pagetop;
       +        printf("# page top %d bot %d size %d physbot %d\n",
       +                pagetop, pagebot, pagesize, physbot);
       +        for (p = slugs; p < slugp; p++) {
       +                switch (p->type) {
       +                // normalize float parameters
       +                case BF:
       +                case UF:
       +                                        // primary goal
       +                        p->parm = max(min(p->parm-pagetop, pagesize), 0);
       +                                        // secondary goal
       +                        p->parm2 = max(min(p->parm2-pagetop, pagesize), 0);
       +                        break;
       +                // normalize need parameters
       +                case NE:
       +                        p->dv = max( min(p->dv, pagesize), 0);
       +                        break;
       +                // mark permissible breaks
       +                case BS:
       +                        markbreak(p);
       +                        break;
       +                }
       +                if (dbg & 1)
       +                        p->dump();
       +        }
       +}
       +
       +void checkout()
       +{
       +        for (slug *p = slugs; p < slugp; p++)
       +                switch (p->type) {
       +                case PT:
       +                case BT:
       +                        p = findend(p);
       +                        break;
       +                case SP:
       +                case VBOX:
       +                        if (p->seen != 1)
       +                                ERROR "%s slug %d seen %d times\n",
       +                                        p->typename(), p->serialno(),
       +                                        p->seen WARNING;
       +                        break;
       +                }
       +}
       +
       +eofrange *lastrange;
       +stream        ptlist, btlist;
       +
       +static slug *makeranges(slug *p, stream *s, int level)
       +{
       +        stream *t;
       +
       +        for ( ; p < slugp; p++)
       +                switch (p->type) {
       +                case VBOX:
       +                        s->append(new vboxrange(p));
       +                        break;
       +                case SP:
       +                        s->append(new sprange(p));
       +                        break;
       +                case BS:
       +                        s->append(new bsrange(p));
       +                        break;
       +                case US:
       +                        s->append(new usrange(p, t = new stream));
       +                        p = makeranges(p+1, t, level+1);
       +                        break;
       +                case BF:
       +                        s->append(new bfrange(p, t = new stream));
       +                        p = makeranges(p+1, t, level+1);
       +                        break;
       +                case UF:
       +                        s->append(new ufrange(p, t = new stream));
       +                        p = makeranges(p+1, t, level+1);
       +                        break;
       +                case PT:
       +                        ptlist.append(new ptrange(p, t = new stream));
       +                        p = makeranges(p+1, t, level+1);
       +                        break;
       +                case BT:
       +                        btlist.append(new btrange(p, t = new stream));
       +                        p = makeranges(p+1, t, level+1);
       +                        break;
       +                case END:
       +                        s->append(new endrange(p));
       +                        return p;
       +                case TM:
       +                        s->append(new tmrange(p));
       +                        break;
       +                case COORD:
       +                        s->append(new coordrange(p));
       +                        break;
       +                case NE:
       +                        if (level) {
       +                                ERROR "Nested NE commands are ignored, line %d\n",
       +                                        p->lineno() WARNING;
       +                                p->dv = 0;
       +                        }
       +                        s->append(new nerange(p));
       +                        break;
       +                case MC:
       +                        s->append(new mcrange(p));
       +                        break;
       +                case CMD:
       +                        if (level)
       +                                ERROR "Nested command ignored, line %d\n",
       +                                        p->lineno() WARNING;
       +                        s->append(new cmdrange(p));
       +                        break;
       +                case PARM:
       +                        if (level)
       +                                ERROR "Nested parameter ignored, line %d\n",
       +                                        p->lineno() WARNING;
       +                        s->append(new parmrange(p));
       +                        break;
       +                case EOF:
       +                        lastrange = new eofrange(p);
       +                        return 0;
       +                }
       +        return p;
       +}
       +
       +static queue text;                        // unexamined input ranges; the real data
       +
       +void startup(FILE *fp)
       +{
       +        doprefix(fp);                        // peel off 'x' commands
       +        readslugs(fp);                        // read everything into slugs[]
       +        fixslugs();                        // measure parameters and clean up
       +        makeranges(slugs, &text, 0);        // add range superstructure
       +        measure(&text);                // heights of nested things
       +        rawmeasure(&text);
       +        while (text.more()) {
       +                range *r = text.dequeue();
       +                if (dbg & 2)
       +                        r->dump();
       +                r->enqueue();
       +        }
       +}
   DIR diff --git a/src/cmd/mpm/range.h b/src/cmd/mpm/range.h
       t@@ -0,0 +1,334 @@
       +const int        NOGOAL = -1;
       +
       +class stream;
       +
       +enum primeflush { NO, YES, EXPECTED, UNEXPECTED };        // mergestream::prime()
       +
       +// Ranges do two things.  They interpose a layer between slugs and the rest
       +// of the program; this is important because of the grossness of the slug
       +// data structure (made necessary by its origins in troff output).  Ranges also
       +// group together other ranges into meaningful chunks like unbreakable stream
       +// objects, floatable objects, and page headers and footers.
       +// Member function height() returns a range's height as of the latest composition.
       +// Member function rawht() returns the range's original height in the input.
       +class range {
       +  protected:
       +        slug        *first;                // earliest slug in range
       +        int        accumV;                // accumulated V to this point
       +  public:
       +        range()                { first = 0; accumV = 0; }
       +        range(slug *p)        { first = p; accumV = 0; }
       +        char        *headstr()                {
       +                return first ? first->headstr() : (char*)""; }
       +        char        *typename()                { return first->typename(); }
       +        int        serialno()                { return first->serialno(); }
       +        int        lineno()                { return first->lineno(); }
       +        virtual void        dump()                { first->dump(); }
       +        virtual void        rdump()                { dump(); }
       +        virtual int        print(int cv, int col)        {
       +                first->slugout(col); return cv; }
       +        virtual int        floatable()        { return 0; }
       +        virtual int        brkafter()        { return 1; }
       +        virtual int        isnested()        { return 0; }
       +        virtual int        issp()                { return 0; }
       +        virtual int        isvbox()        { return 0; }
       +        virtual int        isneed()        { return 0; }
       +        virtual int        iscmd()                { return 0; }
       +        virtual int        cmdtype()        { return -1; }
       +        virtual int        isparm()        { return 0; }
       +        virtual int        parmtype()        { return -1; }
       +        virtual int        parm()                { return -1; }
       +        virtual int        breakable()        { return 0; }
       +        virtual int        forceflush()        { return UNEXPECTED; }
       +        virtual int        pn()                { return 0; }
       +        virtual stream        *children()        { return 0; }        // see page::peeloff()
       +        virtual void        killkids()        { }
       +        virtual void        enqueue(int = 0);
       +        virtual int        height()        { return 0; }
       +        virtual int        rawht()                { return 0; }
       +        virtual int        needht()        { return 0; }
       +        virtual void        reheight(int *, int *)        { }
       +        virtual void        rerawht(int *, int *)        { }
       +        virtual void        setheight(int) { }
       +        virtual void        restore()        { }                // goals of floatables
       +        virtual int        goal()                { return NOGOAL; }
       +        int                accum()                { return accumV; }
       +        void                setaccum(int n)        { accumV = n; }
       +        virtual        void        setgoal(int)        { }
       +        virtual void        pickgoal(int, double)        { }
       +        virtual int        numcol()        { return first->numcol(); }
       +        virtual int        issentinel()        { return 0; }
       +        virtual range        *clone()        { return 0; }
       +        virtual int        breaking()        { return 0; }
       +        virtual void        setbreaking()        { }
       +};
       +
       +class vboxrange : public range {
       +        int        dv;                // inherited from slug
       +        int        base;                // inherited from slug
       +        int        brk;                // 0 => ok to break after, 1 => no break 
       +  public:
       +        vboxrange(slug *p) : range(p) { dv = p->dv; base = p->base; brk = p->parm; }
       +        void        dump() {
       +                printf("#### VBOX brk? %d dv %d ht %d\n", brk, dv, dv+base); }
       +        int        print(int cv, int col) {
       +                printf("V%d\n", cv += dv); first->slugout(col); return cv+base; }
       +        int        brkafter()                { return !brk; }
       +        int        isvbox()                { return 1; }
       +        int        forceflush()                { return NO; }
       +        int        height()                { return dv + base; }
       +        int        rawht()                        { return first->dv + first->base; }
       +        void        reheight(int *cv, int *mv) {
       +                *cv += dv+base; *mv = max(*mv, *cv); }
       +        void        rerawht(int *cv, int *mv) {
       +                *cv += rawht(); *mv = max(*mv, *cv); }
       +};
       +
       +class sprange : public range {
       +        int dv;
       +  public:
       +        sprange(slug *p) : range(p) { dv = first->dv; }
       +        void        dump() {
       +                printf("#### SP dv %d (originally %d)\n", dv, first->dv); }
       +        int        print(int cv, int col)        {
       +                first->slugout(col); return cv + dv; }
       +        int        issp()                        { return 1; }
       +        int        forceflush()                { return YES; }
       +        int        height()                { return dv; }
       +        int        rawht()                        { return first->dv; }
       +        void        reheight(int *, int *);
       +        void        rerawht(int *, int *);
       +        void        setheight(int n)        { dv = n; }
       +};
       +
       +class tmrange : public range {
       +  public:
       +        tmrange(slug *p) : range(p)        { }
       +        int        forceflush()                { return NO; }
       +        int        print(int cv, int col)        { first->slugout(col); return cv; }
       +};
       +
       +class coordrange : public range {
       +  public:
       +        coordrange(slug *p) : range(p)        { }
       +        int        forceflush()                { return NO; }
       +        int        print(int cv, int col)
       +                { first->slugout(col); printf(" Y %d\n", cv); return cv; }
       +};
       +
       +class nerange : public range {
       +  public:
       +        nerange(slug *p) : range(p)        { }
       +        int        isneed()                { return 1; }
       +        int        forceflush()                { return YES; }
       +        int        needht()                { return first->dv; }
       +};
       +
       +class mcrange : public range {
       +  public:
       +        mcrange(slug *p) : range(p)        { }
       +        int        forceflush()                { return YES; }
       +};
       +
       +class cmdrange : public range {
       +  public:
       +        cmdrange(slug *p) : range(p)        { }
       +        int        iscmd()                        { return 1; }
       +        int        forceflush()                { return YES; }
       +        int        cmdtype()                { return first->parm; }
       +};
       +
       +class parmrange : public range {
       +  public:
       +        parmrange(slug *p) : range(p)        { }
       +        int        isparm()                { return 1; }
       +        int        forceflush()                { return YES; }
       +        int        parmtype()                { return first->parm; }
       +        int        parm()                        { return first->parm2; }
       +};
       +
       +class bsrange : public range {
       +  public:
       +        bsrange(slug *p) : range(p)        { }
       +        int        forceflush()                { return NO; }
       +        int        print(int cv, int col)        { first->slugout(col); return cv; }
       +};
       +
       +class endrange : public range {
       +  public:
       +        endrange(slug *p) : range(p)        { }
       +        int        forceflush()                { return UNEXPECTED; }
       +};
       +
       +class eofrange : public range {
       +  public:
       +        eofrange(slug *p) : range(p)        { }
       +        int        forceflush()                { return UNEXPECTED; }
       +};
       +
       +extern eofrange *lastrange;        // the EOF block (trailer, etc.) goes here
       +
       +int measure(stream *);
       +int rawmeasure(stream *);
       +
       +// A nestrange packages together a sequence of ranges, its subrange.
       +// Other parts of the program reach in and alter the dimensions of
       +// some of these ranges, so when the height of a range is requested
       +// it is computed completely afresh.
       +// (Note:  the alternative, of keeping around many copies of ranges
       +// with different dimensions, was abandoned because of the difficulty
       +// of ensuring that exactly one copy of each original range would be
       +// output.)
       +class nestrange : public range {
       +  protected:
       +        stream        *subrange;
       +        int isbreaking;
       +        int rawdv;
       +  public:
       +        nestrange() : range()        { subrange = 0; isbreaking = 0; rawdv = -1; }
       +        nestrange(slug *p, stream *s) : range(p)
       +                                { subrange = s; isbreaking = 0; rawdv = -1; }
       +        void        rdump();
       +        virtual void restore();
       +        stream        *children()        { return subrange; }
       +        void        killkids();
       +        int        height()        { return measure(subrange); }
       +        int        rawht()                { if (rawdv < 0 || isbreaking) rawdv = rawmeasure(subrange);
       +                                        return rawdv; }
       +        void        reheight(int *cv, int *mv) {
       +                        *mv += measure(subrange); *cv = max(*mv, *cv); }
       +        void        rerawht(int *cv, int *mv) {
       +                        *mv += rawht(); *cv = max(*mv, *cv); }
       +        int        isnested()        { return 1; }
       +        int        forceflush()        { return EXPECTED; }
       +        int        print(int cv, int col);
       +        int        breaking()        { return isbreaking; }
       +        void        setbreaking()        { isbreaking++; }
       +};
       +
       +class usrange : public nestrange {
       +  public:
       +        usrange()        { }
       +        usrange(slug *p, stream *s) : nestrange(p, s) {}
       +        void dump() { printf("#### US        dv %d\n", height()); }
       +        range        *clone();
       +};
       +
       +class ufrange : public nestrange {
       +        int        goalV, goal2;
       +  public:
       +        ufrange()        { }
       +        ufrange(slug *p, stream *s) : nestrange(p, s) {
       +                goalV = p->parm; goal2 = p->parm2; }
       +        void         dump() { printf("#### UF   dv %d goal %d goal2 %d\n",
       +                height(), goalV, goal2); }
       +        int        floatable()        { return 1; }
       +        void        enqueue(int = 0);
       +        range        *clone();
       +        int        goal()                { return goalV; }
       +        void        setgoal(int n)        { goalV = goal2 = n; }
       +        void        pickgoal(int acv, double scale);
       +        void        restore()        { goalV = first->parm; goal2 = first->ht; }
       +};
       +
       +class bfrange : public nestrange {
       +        int        goalV, goal2;
       +  public:
       +        bfrange()        { }
       +        bfrange(slug *p, stream *s) : nestrange(p, s) {
       +                goalV = p->parm; goal2 = p->parm2; }
       +        void         dump() { printf("#### BF   dv %d goal %d goal2 %d\n",
       +                height(), goalV, goal2); }
       +        int        floatable()        { return 1; }
       +        void        enqueue(int = 0);
       +        range        *clone();
       +        int        goal()                { return goalV; }
       +        void        setgoal(int n)        { goalV = goal2 = n; }
       +        void        pickgoal(int acv, double scale);
       +        void        restore()        { goalV = first->parm; goal2 = first->parm2; }
       +        int        breakable()        { return 1; }        // can be broken
       +};
       +
       +class ptrange : public nestrange {
       +        int        pgno;
       +  public:
       +        int        pn()        { return pgno; }
       +        ptrange(slug *p, stream *s) : nestrange(p, s) { pgno = p->parm; }
       +        void         dump() { printf("#### PT   pgno %d dv %d\n", pgno, height()); }
       +};
       +
       +class btrange : public nestrange {
       +        int        pgno;
       +  public:
       +        btrange(slug *p, stream *s) : nestrange(p, s) { pgno = p->parm; }
       +        void         dump() { printf("#### BT   pgno %d dv %d\n", pgno, height()); }
       +};
       +
       +// A stream is a sequence of ranges; we use this data structure a lot
       +// to traverse various sequences that crop up in page-making.
       +class stream {
       +  protected:
       +public:
       +        struct strblk {                // ranges are linked by these blocks
       +                strblk        *next;
       +                range        *rp;
       +        };
       +        strblk        *first;
       +        strblk        *last;
       +        strblk        *curr;
       +  public:
       +        stream()                { curr = last = first = 0; }
       +        stream(range *r)        { curr = last = first = new strblk;
       +                                        last->rp = r; last->next = 0; }
       +        void        freeall();        // note:  not a destructor
       +        void        dump();                // top level
       +        void        rdump();        // recursive
       +        int        restoreall();
       +        range        *current()        { return curr->rp; }
       +        range        *next()                { return curr && curr->next ? curr->next->rp : 0; }
       +        void        advance()        { curr = curr->next; }
       +        range        *append(range *r);
       +        void        split();
       +        int        more()                { return curr && curr->rp; }
       +        int        height();
       +        int        rawht();
       +};
       +
       +// A generator iterates through all the ranges of a stream
       +// (not just the root ranges of nestranges).
       +class generator {
       +        stream        s;
       +        generator *child;
       +  public:
       +        generator()                { child = 0; }
       +        generator(stream *sp)        { s = *sp; child = 0; }
       +        range        *next();
       +};
       +
       +extern stream        ptlist, btlist;                // page titles
       +
       +#define INFINITY 1000001
       +
       +// A queue is a distinguished kind of stream.
       +// It keeps its contents in order by the serial numbers of the ranges.
       +// A queue can be blocked from dequeuing something to indicate
       +// that it's not worth considering the queue again on a given page.
       +class queue : public stream {
       +        strblk        *newguy;
       +  protected:
       +        int        blocked;
       +        void        check(char *);
       +  public:
       +        queue() : blocked(0)        { }
       +        range        *enqueue(range *r);
       +        range        *dequeue();
       +        void        block()                { blocked = 1; }
       +        void        unblock()        { blocked = 0; }
       +        int        more()                { return !blocked && stream::more(); }
       +        int        empty()                { return !stream::more(); }
       +        int        serialno()        { return empty() ? INFINITY : current()->serialno(); }
       +};
       +
       +// functions in range.c
       +void checkout();
       +void startup(FILE *);
   DIR diff --git a/src/cmd/mpm/slug.cc b/src/cmd/mpm/slug.cc
       t@@ -0,0 +1,603 @@
       +#include        "misc.h"
       +#include        "slug.h"
       +//#include        <libc.h>
       +#include        <math.h>
       +
       +static char        *bufptr(int);
       +
       +void slug::coalesce()
       +{
       +        (this+1)->dp = dp;        // pretty grimy, but meant to ensure
       +                                // that all output goes out.
       +                        // maybe it has to skip over PT's;
       +                        // some stuff is getting pushed inside PT..END
       +}
       +
       +void slug::neutralize()
       +{
       +        switch (type) {
       +        case PAGE:
       +        case UF:
       +        case BF:
       +        case PARM:
       +                type = NEUTRAL;
       +                coalesce();
       +                break;
       +        default:
       +                ERROR "neutralized %d (%s) with %s\n",
       +                        type, typename(), headstr() WARNING;
       +                break;
       +        }
       +}
       +
       +void slug::dump()        // print contents of a slug
       +{
       +        printf("# %d %-4.4s parm %d dv %d base %d s%d f%d H%d\n#\t\t%s\n",
       +                serialno(), typename(), parm, dv, base,
       +                size, font, hpos, headstr());
       +}
       +
       +char *slug::headstr()
       +{
       +        const int HEADLEN = 65;
       +        static char buf[2*HEADLEN];
       +        int j = 0;
       +        char *s = bufptr(dp);
       +        int n = (this+1)->dp - dp;
       +        if (n >= HEADLEN)
       +                n = HEADLEN;
       +        for (int i = 0; i < n; i++)
       +                switch (s[i]) {
       +                        case '\n':
       +                        case '\t':
       +                        case '\0':
       +                        case ' ':
       +                                break;
       +                        default:
       +                                buf[j++] = s[i];
       +                                break;
       +                }
       +        buf[j] = 0;
       +        return buf;
       +}
       +
       +static char *strindex(char s[], char t[])        // index of earliest t[] in s[]
       +{
       +        for (int i = 0; s[i] != '\0'; i++) {
       +                int j, k;
       +                for (j = i, k = 0; t[k]!='\0' && s[j] == t[k]; j++, k++)
       +                        ;
       +                if (k > 0 && t[k] == '\0')
       +                        return s+i;
       +        }
       +        return 0;
       +}
       +
       +void slug::slugout(int col)
       +{
       +        static int numout = 0;
       +        if (seen++)
       +                ERROR "%s slug #%d seen %d times [%s]\n",
       +                        typename(), serialno(), seen, headstr() WARNING;
       +        if (type == TM) {
       +                char *p;
       +                if ((p = strindex(bufptr(dp), "x X TM ")) != 0)
       +                        p += strlen("x X TM ");                // skip junk
       +                else
       +                        ERROR "strange TM [%s]\n", headstr() FATAL;
       +                fprintf(stderr, "%d\t", userpn);        // page # as prefix
       +                for ( ; p < bufptr((this+1)->dp); p++)
       +                        putc(*p, stderr);
       +        } else if (type == COORD) {
       +                for (char *p = bufptr(dp); p < bufptr((this+1)->dp) && *p != '\n'; p++)
       +                        putc(*p, stdout);
       +                printf(" # P %d X %d", userpn, hpos + col*offset);
       +                return;
       +        } else if (type == VBOX) {
       +                if (numout++ > 0)        // BUG??? might miss something
       +                        printf("s%d\nf%d\n", size, font);
       +                printf("H%d\n", hpos + col*offset);
       +        }
       +        fwrite(bufptr(dp), sizeof(char), (this+1)->dp - dp, stdout);
       +}
       +
       +char *slug::typename()
       +{
       +        static char buf[50];
       +        char *p = buf;                // return value
       +        switch(type) {
       +        case EOF:        p = "EOF"; break;
       +        case VBOX:        p = "VBOX"; break;
       +        case SP:        p = "SP"; break;
       +        case BS:        p = "BS"; break;
       +        case US:        p = "US"; break;
       +        case BF:        p = "BF"; break;
       +        case UF:        p = "UF"; break;
       +        case PT:        p = "PT"; break;
       +        case BT:        p = "BT"; break;
       +        case END:        p = "END"; break;
       +        case NEUTRAL:        p = "NEUT"; break;
       +        case PAGE:        p = "PAGE"; break;
       +        case TM:        p = "TM"; break;
       +        case COORD:        p = "COORD"; break;
       +        case NE:        p = "NE"; break;
       +        case CMD:        p = "CMD"; break;
       +        case PARM:        p = "PARM"; break;
       +        default:        sprintf(buf, "weird type %d", type);
       +        }
       +        return p;
       +}
       +
       +// ================================================================================
       +
       +//         troff output-specific functions
       +
       +// ================================================================================
       +
       +const int        DELTABUF = 500000;        // grow the input buffer in chunks
       +
       +static char        *inbuf = 0;                // raw text input collects here
       +static int        ninbuf = 0;                // byte count for inbuf
       +static char        *inbp = 0;                // next free slot in inbuf
       +int                linenum = 0;                // input line number
       +
       +static inline void addc(int c) { *inbp++ = c; }
       +
       +static void adds(char *s)
       +{
       +        for (char *p = s; *p; p++)
       +                addc(*p);
       +}
       +
       +static char *getutf(FILE *fp)        // get 1 utf-encoded char (might be multiple bytes)
       +{
       +        static char buf[100];
       +        char *p = buf;
       +
       +        for (*p = 0; (*p++ = getc(fp)) != EOF; ) {
       +                *p = 0;
       +                if (mblen(buf, sizeof buf) > 0)        // found a valid character
       +                        break;
       +        }
       +        return buf;
       +}
       +
       +static char *bufptr(int n) { return inbuf + n; }  // scope of inbuf is too local
       +
       +static inline int wherebuf() { return inbp - inbuf; }
       +
       +static char *getstr(char *p, char *temp)
       +{                // copy next non-blank string from p to temp, update p
       +        while (*p == ' ' || *p == '\t' || *p == '\n')
       +                p++;
       +        if (*p == '\0') {
       +                temp[0] = 0;
       +                return(NULL);
       +        }
       +        while (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\0')
       +                *temp++ = *p++;
       +        *temp = '\0';
       +        return(p);
       +}
       +
       +/***************************************************************************
       +   bounding box of a circular arc             Eric Grosse  24 May 84
       +
       +Conceptually, this routine generates a list consisting of the start,
       +end, and whichever north, east, south, and west points lie on the arc.
       +The bounding box is then the range of this list.
       +    list = {start,end}
       +    j = quadrant(start)
       +    k = quadrant(end)
       +    if( j==k && long way 'round )  append north,west,south,east
       +    else
       +      while( j != k )
       +         append center+radius*[j-th of north,west,south,east unit vectors]
       +         j += 1  (mod 4)
       +    return( bounding box of list )
       +The following code implements this, with simple optimizations.
       +***********************************************************************/
       +
       +static int quadrant(double x, double y)
       +{
       +        if (     x>=0.0 && y> 0.0) return(1);
       +        else if( x< 0.0 && y>=0.0) return(2);
       +        else if( x<=0.0 && y< 0.0) return(3);
       +        else if( x> 0.0 && y<=0.0) return(4);
       +        else                           return 0;        /* shut up lint */
       +}
       +
       +static double xmin, ymin, xmax, ymax;        // used by getDy
       +
       +static void arc_extreme(double x0, double y0, double x1, double y1, double xc, double yc)
       +                /* start, end, center */
       +{                /* assumes center isn't too far out */
       +        double r;
       +        int j, k;
       +        printf("#start %g,%g, end %g,%g, ctr %g,%g\n", x0,y0, x1,y1, xc,yc);
       +        y0 = -y0; y1 = -y1; yc = -yc;        // troff's up is eric's down
       +        x0 -= xc; y0 -= yc;        /* move to center */
       +        x1 -= xc; y1 -= yc;
       +        xmin = (x0<x1)?x0:x1; ymin = (y0<y1)?y0:y1;
       +        xmax = (x0>x1)?x0:x1; ymax = (y0>y1)?y0:y1;
       +        r = sqrt(x0*x0 + y0*y0);
       +        if (r > 0.0) {
       +                j = quadrant(x0,y0);
       +                k = quadrant(x1,y1);
       +                if (j == k && y1*x0 < x1*y0) {
       +                        /* viewed as complex numbers, if Im(z1/z0)<0, arc is big */
       +                        if( xmin > -r) xmin = -r; if( ymin > -r) ymin = -r;
       +                        if( xmax <  r) xmax =  r; if( ymax <  r) ymax =  r;
       +                } else {
       +                        while (j != k) {
       +                                switch (j) {
       +                                case 1: if( ymax <  r) ymax =  r; break; /* north */
       +                                case 2: if( xmin > -r) xmin = -r; break; /* west */
       +                                case 3: if( ymin > -r) ymin = -r; break; /* south */
       +                                case 4: if( xmax <  r) xmax =  r; break; /* east */
       +                                }
       +                                j = j%4 + 1;
       +                        }
       +                }
       +        }
       +        xmin += xc; ymin += yc; ymin = -ymin;
       +        xmax += xc; ymax += yc; ymax = -ymax;
       +}
       +
       +
       +static int getDy(char *p, int *dx, int *maxv)
       +                                // figure out where we are after a D'...'
       +{
       +        int x, y, x1, y1;        // for input values
       +        char temp[50];
       +        p++;                // get to command letter
       +        switch (*p++) {
       +        case 'l':        // line
       +                sscanf(p, "%d %d", dx, &y);
       +                return *maxv = y;
       +        case 'a':        // arc
       +                sscanf(p, "%d %d %d %d", &x, &y, &x1, &y1);
       +                *dx = x1 - x;
       +                arc_extreme(0, 0, x+x1, y+y1, x, y);        // sets [xy][max|min]
       +                printf("#arc bounds x %g, %g; y %g, %g\n",
       +                        xmin, xmax, ymin, ymax);
       +                *maxv = (int) (ymin+0.5);
       +                return y + y1;
       +        case '~':        // spline
       +                for (*dx = *maxv = y = 0; (p=getstr(p, temp)) != NULL; ) {
       +                                                // above getstr() gets x value
       +                        *dx += atoi(temp);
       +                        p = getstr(p, temp);        // this one gets y value
       +                        y += atoi(temp);
       +                        *maxv = max(*maxv, y);        // ok???
       +                        if (*p == '\n' || *p == 0)        // input is a single line;
       +                                break;                        // don't walk off end if realloc
       +                }
       +                return y;
       +        case 'c':        // circle, ellipse
       +                sscanf(p, "%d", dx);
       +                *maxv = *dx/2;                // high water mark is ht/2
       +                return 0;
       +        case 'e':
       +                sscanf(p, "%d %d", dx, &y);
       +                *maxv = y/2;                // high water mark is ht/2
       +                return 0;
       +        default:        // weird stuff
       +                return 0;
       +        }
       +}
       +
       +static int serialnum = 0;
       +
       +slug eofslug()
       +{
       +        slug ret;
       +        ret.serialnum = serialnum;
       +        ret.type = EOF;
       +        ret.dp = wherebuf();
       +        return ret;
       +}
       +
       +slug getslug(FILE *fp)
       +{
       +        if (inbuf == NULL) {
       +                if ((inbuf = (char *) malloc(ninbuf = DELTABUF)) == NULL)
       +                        ERROR "no room for %d character input buffer\n", ninbuf FATAL;
       +                inbp = inbuf;
       +        }
       +        if (wherebuf() > ninbuf-5000) {
       +                // this is still flaky -- lines can be very long
       +                int where = wherebuf();        // where we were
       +                if ((inbuf = (char *) realloc(inbuf, ninbuf += DELTABUF)) == NULL)
       +                        ERROR "no room for %d character input buffer\n", ninbuf FATAL;
       +                ERROR "grew input buffer to %d characters\n", ninbuf WARNING;
       +                inbp = inbuf + where;        // same offset in new array
       +        }
       +        static int baseV = 0;        // first V command of preceding slug
       +        static int curV = 0, curH = 0;
       +        static int font = 0, size = 0;
       +        static int baseadj = 0;
       +        static int ncol = 1, offset = 0;        // multi-column stuff
       +        char str[1000], str2[1000], buf[3000], *p;
       +        int firstV = 0, firstH = 0;
       +        int maxV = curV;
       +        int ocurV = curV, mxv = 0, dx = 0;
       +        int sawD = 0;                // > 0 if have seen D...
       +        slug ret;
       +        ret.serialnum = serialnum++;
       +        ret.type = VBOX;        // use the same as last by default
       +        ret.dv = curV - baseV;
       +        ret.hpos = curH;
       +        ret.base =  ret.parm = ret.parm2 = ret.seen = 0;
       +        ret.font = font;
       +        ret.size = size;
       +        ret.dp = wherebuf();
       +        ret.ncol = ncol;
       +        ret.offset = offset;
       +        ret.linenum = linenum;        // might be low
       +
       +        for (;;) {
       +                int c, m, n;        // for input values
       +                int sign;                // hoisted from case 'h' below
       +                switch (c = getc(fp)) {
       +                case EOF:
       +                        ret.type = EOF;
       +                        ret.dv = 0;
       +                        if (baseadj)
       +                                printf("# adjusted %d bases\n", baseadj);
       +                        printf("# %d characters, %d lines\n", wherebuf(), linenum);
       +                        return ret;
       +                case 'V':
       +                        fscanf(fp, "%d", &n);
       +                        if (firstV++ == 0) {
       +                                ret.dv = n - baseV;
       +                                baseV = n;
       +                        } else {
       +                                sprintf(buf, "v%d", n - curV);
       +                                adds(buf);
       +                        }
       +                        curV = n;
       +                        maxV = max(maxV, curV);
       +                        break;
       +                case 'H':                // absolute H motion
       +                        fscanf(fp, "%d", &n);
       +                        if (firstH++ == 0) {
       +                                ret.hpos = n;
       +                        } else {
       +                                sprintf(buf, "h%d", n - curH);
       +                                adds(buf);
       +                        }
       +                        curH = n;
       +                        break;
       +                case 'h':                // relative H motion
       +                        addc(c);
       +                        sign = 1;
       +                        if ((c = getc(fp)) == '-') {
       +                                addc(c);
       +                                sign = -1;
       +                                c = getc(fp);
       +                        }
       +                        for (n = 0; isdigit(c); c = getc(fp)) {
       +                                addc(c);
       +                                n = 10 * n + c - '0';
       +                        }
       +                        curH += n * sign;
       +                        ungetc(c, fp);
       +                        break;
       +                case 'x':        // device control: x ...
       +                        addc(c);
       +                        fgets(buf, (int) sizeof(buf), fp);
       +                        linenum++;
       +                        adds(buf);
       +                        if (buf[0] == ' ' && buf[1] == 'X') {        // x X ...
       +                                if (2 != sscanf(buf+2, "%s %d", str, &n))
       +                                        n = 0;
       +                                if (eq(str, "SP")) {        // X SP n
       +                                        ret.type = SP;        // paddable SPace
       +                                        ret.dv = n;        // of height n
       +                                } else if (eq(str, "BS")) {
       +                                        ret.type = BS;        // Breakable Stream
       +                                        ret.parm = n;        // >=n VBOXES on a page
       +                                } else if (eq(str, "BF")) {
       +                                        ret.type = BF;        // Breakable Float
       +                                        ret.parm = ret.parm2 = n;
       +                                                        // n = pref center (as UF)
       +                                } else if (eq(str, "US")) {
       +                                        ret.type = US;        // Unbreakable Stream
       +                                        ret.parm = n;
       +                                } else if (eq(str, "UF")) {
       +                                        ret.type = UF;        // Unbreakable Float
       +                                        ret.parm = ret.parm2 = n;
       +                                                        // n = preferred center
       +                                                        // to select several,
       +                                                        // use several UF lines
       +                                } else if (eq(str, "PT")) {
       +                                        ret.type = PT;        // Page Title
       +                                        ret.parm = n;
       +                                } else if (eq(str, "BT")) {
       +                                        ret.type = BT;        // Bottom Title
       +                                        ret.parm = n;
       +                                } else if (eq(str, "END")) {
       +                                        ret.type = END;
       +                                        ret.parm = n;
       +                                } else if (eq(str, "TM")) {
       +                                        ret.type = TM;        // Terminal Message
       +                                        ret.dv = 0;
       +                                } else if (eq(str, "COORD")) {
       +                                        ret.type = COORD;// page COORDinates
       +                                        ret.dv = 0;
       +                                } else if (eq(str, "NE")) {
       +                                        ret.type = NE;        // NEed to break page
       +                                        ret.dv = n;        // if <n units left
       +                                } else if (eq(str, "MC")) {
       +                                        ret.type = MC;        // Multiple Columns
       +                                        sscanf(buf+2, "%s %d %d",
       +                                                str, &ncol, &offset);
       +                                        ret.ncol = ncol;
       +                                        ret.offset = offset;
       +                                } else if (eq(str, "CMD")) {
       +                                        ret.type = CMD;        // CoMmaNd
       +                                        sscanf(buf+2, "%s %s", str2, str);
       +                                        if (eq(str, "FC"))        // Freeze 2-Col
       +                                                ret.parm = FC;
       +                                        else if (eq(str, "FL"))        // FLush
       +                                                ret.parm = FL;
       +                                        else if (eq(str, "BP"))        // Break Page
       +                                                ret.parm = BP;
       +                                        else ERROR "unknown command %s\n",
       +                                                str WARNING;
       +                                } else if (eq(str, "PARM")) {
       +                                        ret.type = PARM;// PARaMeter
       +                                        sscanf(buf+2, "%s %s %d", str2, str, &ret.parm2);
       +                                        if (eq(str, "NP"))        // New Page
       +                                                ret.parm = NP;
       +                                        else if (eq(str, "FO"))        // FOoter
       +                                                ret.parm = FO;
       +                                        else if (eq(str, "PL")) // Page Length
       +                                                ret.parm = PL;
       +                                        else if (eq(str, "MF")) // MinFull
       +                                                ret.parm = MF;
       +                                        else if (eq(str, "CT")) // ColTol
       +                                                ret.parm = CT;
       +                                        else if (eq(str, "WARN")) //WARNings?
       +                                                ret.parm = WARN;
       +                                        else if (eq(str, "DBG"))// DeBuG
       +                                                ret.parm = DBG;
       +                                        else ERROR "unknown parameter %s\n",
       +                                                str WARNING;
       +                                } else
       +                                        break;                // out of switch
       +                                if (firstV > 0)
       +                                        ERROR "weird x X %s in mid-VBOX\n",
       +                                                str WARNING;
       +                                return ret;
       +                        }
       +                        break;
       +                case 'n':        // end of line
       +                        fscanf(fp, "%d %d", &n, &m);
       +                        ret.ht = n;
       +                        ret.base = m;
       +                        getc(fp);        // newline
       +                        linenum++;
       +                        sprintf(buf, "n%d %d\n", ret.ht, ret.base);
       +                        adds(buf);
       +                        if (!firstV++)
       +                                baseV = curV;
       +                        // older incarnations of this program used ret.base
       +                        // in complicated and unreliable ways;
       +                        // example:  if ret.ht + ret.base < ret.dv, ret.base = 0
       +                        // this was meant to avoid double-counting the space
       +                        // around displayed equations; it didn't work
       +                        // Now, we believe ret.base = 0, otherwise we give it
       +                        // a value we have computed.
       +                        if (ret.base == 0 && sawD == 0)
       +                                return ret;        // don't fiddle 0-bases
       +                        if (ret.base != maxV - baseV) {
       +                                ret.base = maxV - baseV;
       +                                baseadj++;
       +                        }
       +                        if (ret.type != VBOX)
       +                                ERROR "%s slug (type %d) has base = %d\n",
       +                                        ret.typename(), ret.type, ret.base WARNING;
       +                        return ret;
       +                case 'p':        // new page
       +                        fscanf(fp, "%d", &n);
       +                        ret.type = PAGE;
       +                        curV = baseV = ret.dv = 0;
       +                        ret.parm = n;        // just in case someone needs it
       +                        return ret;
       +                case 's':        // size change snnn
       +                        fscanf(fp, "%d", &size);
       +                        sprintf(buf, "s%d\n", size);
       +                        adds(buf);
       +                        break;
       +                case 'f':        // font fnnn
       +                        fscanf(fp, "%d", &font);
       +                        sprintf(buf, "f%d\n", font);
       +                        adds(buf);
       +                        break;
       +                case '\n':
       +                        linenum++;
       +                        /* fall through */
       +                case ' ':
       +                        addc(c);
       +                        break;
       +                case '0': case '1': case '2': case '3': case '4':
       +                case '5': case '6': case '7': case '8': case '9':
       +                        // two motion digits plus a character
       +                        addc(c);
       +                        n = c - '0';
       +                        addc(c = getc(fp));
       +                        curH += 10 * n + c - '0';
       +                        adds(getutf(fp));
       +                        if (!firstV++)
       +                                baseV = curV;
       +                        break;
       +                case 'c':        // single ascii character
       +                        addc(c);
       +                        adds(getutf(fp));
       +                        if (!firstV++)
       +                                baseV = curV;
       +                        break;
       +                case 'C':        // Cxyz\n
       +                case 'N':        // Nnnn\n
       +                        addc(c);
       +                        while ((c = getc(fp)) != ' ' && c != '\n')
       +                                addc(c);
       +                        addc(c);
       +                        if (!firstV++)
       +                                baseV = curV;
       +                        linenum++;
       +                        break;
       +                case 'D':        // draw function: D.*\n
       +                        sawD++;
       +                        p = bufptr(wherebuf());        // where does the D start
       +                        addc(c);
       +                        while ((c = getc(fp)) != '\n')
       +                                addc(c);
       +                        addc(c);
       +                        if (!firstV++)
       +                                baseV = curV;
       +                        ocurV = curV, mxv = 0, dx = 0;
       +                        curV += getDy(p, &dx, &mxv);        // figure out how big it is
       +                        maxV = max(max(maxV, curV), ocurV+mxv);
       +                        curH += dx;
       +                        linenum++;
       +                        break;
       +                case 'v':        // relative vertical vnnn
       +                        addc(c);
       +                        if (!firstV++)
       +                                baseV = curV;
       +                        sign = 1;
       +                        if ((c = getc(fp)) == '-') {
       +                                addc(c);
       +                                sign = -1;
       +                                c = getc(fp);
       +                        }
       +                        for (n = 0; isdigit(c); c = getc(fp)) {
       +                                addc(c);
       +                                n = 10 * n + c - '0';
       +                        }
       +                        ungetc(c, fp);
       +                        curV += n * sign;
       +                        maxV = max(maxV, curV);
       +                        addc('\n');
       +                        break;
       +                case 'w':        // word space
       +                        addc(c);
       +                        break;
       +                case '#':        // comment
       +                        addc(c);
       +                        while ((c = getc(fp)) != '\n')
       +                                addc(c);
       +                        addc('\n');
       +                        linenum++;
       +                        break;
       +                default:
       +                        ERROR "unknown input character %o %c (%50.50s)\n",
       +                                c, c, bufptr(wherebuf()-50) WARNING;
       +                        abort();
       +                        break;
       +                }
       +        }
       +}
   DIR diff --git a/src/cmd/mpm/slug.h b/src/cmd/mpm/slug.h
       t@@ -0,0 +1,74 @@
       +enum slugtypes {
       +        NONE,                // can't happen
       +        VBOX,                // Vertical Box -- printable stuff
       +        SP,                // paddable SPace
       +        BS,                // start Breakable Stream
       +        US,                // start Unbreakable Stream
       +        BF,                // start Breakable Float
       +        UF,                // start Unbreakable Float
       +        PT,                // start Page Top material (header)
       +        BT,                // start page BoTtom material (footer)
       +        END,                // ENDs of groups
       +        NEUTRAL,        // NEUTRALized slugs can do no harm (cf. CIA)
       +        PAGE,                // beginning of PAGE in troff input
       +        TM,                // Terminal Message to appear during output
       +        COORD,                // output page COORDinates
       +        NE,                // NEed command
       +        MC,                // Multiple-Column command
       +        CMD,                // misc CoMmanDs:  FC, FL, BP
       +        PARM,                // misc PARaMeters:  NP, FO
       +        LASTTYPE        // can't happen either
       +};
       +
       +enum cmdtypes {
       +        FC,        // Freeze 2-Column material
       +        FL,        // FLush all floats before reading more stream
       +        BP        // Break Page
       +};
       +
       +enum parmtypes {
       +        NP,        // distance of top margin from page top (New Page)
       +        FO,        // distance of bottom margin from page top (FOoter)
       +        PL,        // distance of physical page bottom from page top (Page Length)
       +        MF,        // minimum fullness required for padding
       +        CT,        // tolerance for division into two columns
       +        WARN,        // warnings to stderr?        
       +        DBG        // debugging flag
       +};
       +
       +class slug {
       +        int        serialnum;
       +        int        dp;                // offset of data for this slug in inbuf
       +        int        linenum;        // input line number (approx) for this slug
       +        short        font;                // font in effect at slug beginning
       +        short        size;                // size in effect at slug beginning
       +        short        seen;                // 0 until output
       +        short        ncol;                // number of columns (1 or 2)
       +        short        offset;                // horizontal offset for 2 columns
       +  public:
       +        short        type;                // VBOX, PP, etc.
       +        short        parm;                // parameter
       +        short        base;                // "depth" of this slug (from n command)
       +        int        hpos;                // abs horizontal position
       +        int        dv;                // height of this slug above its input Vpos
       +        union {
       +                int        ht;        // "height" of this slug (from n command)
       +                int        parm2;        // second parameter, since only VBOXes have ht
       +        };
       +        friend        slug getslug(FILE *);
       +        friend        void checkout();
       +        friend        slug eofslug();
       +        void        coalesce();        // with next slug in array slugs[]
       +        void        neutralize();        // render this one a no-op
       +        void        dump();                // dump its contents for debugging
       +        char        *headstr();        // string value of text
       +        void        slugout(int);        // add the slug to the output
       +        char        *typename();        // printable slug type
       +        int        serialno()        { return serialnum; }
       +        int        numcol()        { return ncol; }
       +        int        lineno()        { return linenum; }
       +};
       +
       +// functions in slug.c
       +slug        eofslug();
       +slug        getslug(FILE *);
   DIR diff --git a/src/cmd/mpm/tmac.pm b/src/cmd/mpm/tmac.pm
       t@@ -0,0 +1,961 @@
       +.\" 10/22/92 activate next line before installing
       +.pi /$objtype/bin/aux/pm
       +.
       +.                \" IZ - initialization
       +.de IZ
       +.fp 1 R                        \" force a font out into prefix
       +.nr PS 10                \" point size
       +.nr VS 12                \" line spacing
       +.ps \\n(PS
       +.ie \\n(VS>=41 .vs \\n(VSu
       +.el .vs \\n(VSp
       +.nr LL 6i                \" line length
       +.ll \\n(LLu
       +.nr LT \\n(.l                \" title length
       +.lt \\n(LTu
       +.if !\\n(HM .nr HM 1i   \" top of page
       +.if !\\n(FM .nr FM 1i        \" footer margin
       +.if !\\n(FO .nr FO \\n(.p-\\n(FM        \" bottom of page
       +.                        \" to set text ht to N, set FO to N + \n(HM.  default is 10i
       +.pl 32767u                \" safety first: big pages for pm
       +.if !\\n(PO .nr PO \\n(.ou        \" page offset
       +.nr PI 5n                \" .PP paragraph indent
       +.nr QI 5n                \" .QS indent
       +.nr DI 5n                \" .DS indent
       +.nr PD 0.3v                \" paragraph vertical separation
       +.nr TS 0.5v                \" space around tables
       +.nr Kf 0.5v                \" space around .KF/.KE
       +.nr Ks 0.5v                \" space around .KS/.KE
       +.
       +.nr P1 .4i                \" indent for .P1/.P2
       +.nr dP 1                \" delta point size for programs in .P1/.P2
       +.nr dV 2p                \" delta vertical for programs
       +.nr dT 8                \" delta tab stop for programs
       +.nr DV .5v                \" space before start of program
       +.nr IP 0                \" ?
       +.nr IR 0                \" ?
       +.nr I1 \\n(PIu
       +.ev 1
       +.if !\\n(FL .nr FL \\n(LLu        \" footnote length
       +.ll \\n(FLu
       +.ps 8                        \" text size & leading in footnote
       +.vs 10p
       +.ev
       +.if \\*(CH .ds CH "\(hy \\\\n(PN \(hy
       +.ds # #\\\\n(.c \\\\n(.F
       +.
       +.
       +.ME        \" initialize date strings
       +.rm ME
       +.        \"  accents:  \*'e \*`e \*:u \*^e \*~n \*va \*,c
       +.ds ' \h'\w'e'u*4/10'\z\(aa\h'-\w'e'u*4/10'
       +.ds ` \h'\w'e'u*4/10'\z\(ga\h'-\w'e'u*4/10'
       +.ds : \\v'-0.6m'\\h'(1u-(\\\\n(.fu%2u))*0.13m+0.00m'\\z.\\h'0.2m'\\z.\\h'-((1u-(\\\\n(.fu%2u))*0.13m+0.20m)'\\v'0.6m'
       +.ds ^ \\\\k:\\h'-\\\\n(.fu+1u/2u*2u+\\\\n(.fu-1u*0.13m+0.06m'\\z^\\h'|\\\\n:u'
       +.ds ~ \\\\k:\\h'-\\\\n(.fu+1u/2u*2u+\\\\n(.fu-1u*0.13m+0.06m'\\z~\\h'|\\\\n:u'
       +.ds v \\\\k:\\\\h'+\\\\w'e'u/4u'\\\\v'-0.6m'\\\\s6v\\\\s0\\\\v'0.6m'\\\\h'|\\\\n:u'
       +.ds , \\\\k:\\\\h'\\\\w'c'u*0.4u'\\\\z,\\\\h'|\\\\n:u'
       +..
       +.
       +.
       +.                \" SP - generate paddable space
       +.de SP
       +.br
       +.nr X 1v
       +.if \\n(.$ .nr X \\$1v
       +.ie '\\$2'exactly' \{\
       +\v'\\nXu'\ \h'-\w'\ 'u'\c
       +.sp \\$1\}
       +.el .X "SP \\nX \\$2"
       +..
       +.                \" NE - need space on this page
       +.de NE
       +.nr X 1v
       +.if \\n(.$ .nr X \\$1v
       +.X "NE \\nX \\$2"
       +..
       +.                \" BP, FL, FC - begin page, flush figures, flush column
       +.de BP
       +.br
       +.X CMD BP
       +..
       +.de FL
       +.br
       +.X CMD FL
       +..
       +.de FC
       +.br
       +.X CMD FC
       +..
       +.                \" X - generate an x X ... command in the output
       +.de X
       +....ie '\\n(.z'' \\!x X \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
       +....el \\!.X "\\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
       +...
       +.if !'\\n(.z'' .if \\n(.$=1 \\!.X "\\$1
       +.if !'\\n(.z'' .if \\n(.$=2 \\!.X "\\$1 \\$2
       +.if !'\\n(.z'' .if \\n(.$=3 \\!.X "\\$1 \\$2 \\$3
       +.if !'\\n(.z'' .if \\n(.$>3 \\!.X "\\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
       +.if '\\n(.z'' .if \\n(.$=1 \\!x X \\$1 \\*#
       +.if '\\n(.z'' .if \\n(.$=2 \\!x X \\$1 \\$2 \\*#
       +.if '\\n(.z'' .if \\n(.$=3 \\!x X \\$1 \\$2 \\$3 \\*#
       +.if '\\n(.z'' .if \\n(.$=4 \\!x X \\$1 \\$2 \\$3 \\$4 \\*#
       +.if '\\n(.z'' .if \\n(.$>4 \\!x X \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 \\*#
       +..
       +.                \" DA - force date
       +.de DA
       +.if \\n(.$ .ds DY \\$1 \\$2 \\$3 \\$4
       +.ds CF \\*(DY
       +..
       +.                \" ND - set new or no date
       +.de ND
       +.ds DY \\$1 \\$2 \\$3 \\$4
       +.rm CF
       +..
       +.de ME                \" ME - set month strings
       +.if \\n(mo-0 .ds MO January
       +.if \\n(mo-1 .ds MO February
       +.if \\n(mo-2 .ds MO March
       +.if \\n(mo-3 .ds MO April
       +.if \\n(mo-4 .ds MO May
       +.if \\n(mo-5 .ds MO June
       +.if \\n(mo-6 .ds MO July
       +.if \\n(mo-7 .ds MO August
       +.if \\n(mo-8 .ds MO September
       +.if \\n(mo-9 .ds MO October
       +.if \\n(mo-10 .ds MO November
       +.if \\n(mo-11 .ds MO December
       +.if \\n(dw-0 .ds DW Sunday
       +.if \\n(dw-1 .ds DW Monday
       +.if \\n(dw-2 .ds DW Tuesday
       +.if \\n(dw-3 .ds DW Wednesday
       +.if \\n(dw-4 .ds DW Thursday
       +.if \\n(dw-5 .ds DW Friday
       +.if \\n(dw-6 .ds DW Saturday
       +.if "\\*(DY"" .ds DY \\*(MO \\n(dy, 19\\n(yr
       +..
       +.                \" FP - font position for a family
       +.de FP
       +.if '\\$1'palatino'\{\
       +.        fp 1 PA
       +.        fp 2 PI
       +.        fp 3 PB
       +.        fp 4 PX\}
       +.if '\\$1'century'\{\
       +.        ie '\\*(.T'202'\{\
       +.                fp 1 NR Centsb
       +.                fp 2 NI CentI
       +.                fp 3 NB CentB
       +.                fp 4 NX CentBI\}
       +.        el \{\
       +.                fp 1 NR
       +.                fp 2 NI
       +.                fp 3 NB
       +.                fp 4 NX\}\}
       +.if '\\$1'helvetica'\{\
       +.        fp 1 H
       +.        fp 2 HI
       +.        fp 3 HB
       +.        fp 4 HX\}
       +.if '\\$1'bembo'\{\
       +.        ie '\\*(.T'202'\{\
       +.                fp 1 B1 Bembo
       +.                fp 2 B2 BemboI
       +.                fp 3 B3 BemboB
       +.                fp 4 B4 BemboBI\}
       +.        el \{\
       +.                fp 1 B1
       +.                fp 2 B2
       +.                fp 3 B3
       +.                fp 4 B4\}\}
       +.if '\\$1'optima'\{\
       +.        fp 1 R Optima
       +.        fp 2 I OptimaI
       +.        fp 3 B OptimaB
       +.        fp 4 BI OptimaBI\}
       +.if '\\$1'souvenir'\{\
       +.        fp 1 R Souvenir
       +.        fp 2 I SouvenirI
       +.        fp 3 B SouvenirB
       +.        fp 4 BI SouvenirBI\}
       +.if '\\$1'melior'\{\
       +.        fp 1 R Melior
       +.        fp 2 I MeliorI
       +.        fp 3 B MeliorB
       +.        fp 4 BI MeliorBI\}
       +.if '\\$1'times'\{\
       +.        fp 1 R
       +.        fp 2 I
       +.        fp 3 B
       +.        fp 4 BI\}
       +..
       +.                \" TL - title
       +.de TL
       +.br
       +.if !\\n(1T .BG
       +....hy 0
       +.ft 3
       +.ps \\n(PS+2p
       +.vs \\n(VS+2p
       +.ll \\n(LLu
       +.ce 100                \" turned off in .RT
       +.sp .5i
       +..
       +.                \" AU - remember author(s)
       +.de AU
       +.ft 1
       +.ps \\n(PS
       +.ie \\n(VS>=41 .vs \\n(VSu
       +.el .vs \\n(VSp
       +.SP .5
       +..
       +.                \" AI - author's institution
       +.de AI
       +.SP .25
       +.ft 2
       +..
       +.                \" AB - begin abstract
       +.de AB
       +.nr AB 1          \" we're in abstract
       +.if !\\n(1T .BG
       +.ft 1
       +.ps \\n(PS
       +.vs \\n(VSp
       +.ce
       +.in +\\n(.lu/12u
       +.ll -\\n(.lu/12u
       +.SP 1
       +.ie \\n(.$ \\$1
       +.el ABSTRACT
       +.SP .75 
       +.RT
       +..
       +.                \" AE - end of abstract
       +.de AE
       +.br
       +.nr AB 0
       +.in 0
       +.ll \\n(LLu
       +.ps \\n(PS
       +.ie \\n(VS>=41 .vs \\n(VSu
       +.el .vs \\n(VSp
       +.SP
       +..
       +.                \" 2C - 2 columns
       +.de 2C
       +.MC 2
       +..
       +.                \" 1C - 1 column
       +.de 1C
       +.MC 1
       +..
       +.                \" MC - multiple columns
       +.de MC
       +.br
       +.if \\n(1T .RT
       +.if \\n(1T .NP
       +.if !\\n(OL .nr OL \\n(LL
       +.if \\n(CW=0 .nr CW \\n(LL*7/15
       +.if \\n(GW=0 .nr GW \\n(LL-(2*\\n(CW)
       +.nr x \\n(CW+\\n(GW
       +.if "\\$1"" .MC 2
       +.if \\$1=1 \{\
       +.        X MC 1 0
       +.        nr LL \\n(OLu\}
       +.if \\$1=2 \{\
       +.        X MC 2 \\nx
       +.        nr LL \\n(CWu\}
       +.ll \\n(LLu
       +.if \\$1>2 .tm -mpm can't handle more than two columns
       +.if \\n(1T .RT
       +..
       +.                \" TS - table start, TE - table end;  also TC, TQ, TH
       +.de TS
       +.br
       +.if !\\n(1T .RT
       +.SP \\n(TSu TS
       +.X "US TS
       +.if \\$1H .TQ
       +.nr IX 1
       +..
       +.de TC
       +.nr TZ \\n(.lu
       +.if \\n(.$ .nr TZ \\$1n
       +.ta \\n(TZuR
       +..
       +.de TD
       +.LP
       +.nr TZ 0
       +..
       +.de TQ
       +.di TT
       +.nr IT 1
       +..
       +.de TH
       +.if \\n(.d>0.5v \{\
       +.        nr T. 0
       +.        T# 0\}
       +.di
       +.nr TQ \\n(.i
       +.nr HT 1
       +.in 0
       +.mk #a
       +.mk #b
       +.mk #c
       +.mk #d
       +.mk #e
       +.mk #f
       +.TT
       +.in \\n(TQu
       +.mk #T
       +..
       +.                \" TE - table end
       +.de TE
       +.nr IX 0
       +.if \\n(IT .if !\\n(HT \{\
       +.        di
       +.        nr EF \\n(.u
       +.        nf
       +.        TT
       +.        if \\n(EF .fi\}
       +.nr IT 0
       +.nr HT 0
       +.rm a+ b+ c+ d+ e+ f+ g+ h+ i+ j+ k+ l+ n+ m+
       +.rr 32 33 34 35 36 37 38 40 79 80 81 82
       +.rr a| b| c| d| e| f| g| h| i| j| k| l| m|
       +.rr a- b- c- d- e- f- g- h- i- j- k- l- m-
       +.X "END US TE
       +.SP \\n(TSu TE
       +.bp
       +..
       +.                \" EQ - equation, breakout and display
       +.de EQ
       +.nr EF \\n(.u
       +.rm EE
       +.nr LE 1        \" 1 is center
       +.ds EL \\$1
       +.if "\\$1"L" \{\
       +.        ds EL \\$2
       +.        nr LE 0\}
       +.if "\\$1"C" .ds EL \\$2
       +.if "\\$1"R" \{\
       +.        ds EL \\$2 \" 2 is right adjust
       +.        nr LE 2\}
       +.if "\\$1"I" \{\
       +.        nr LE 0
       +.        if "\\$3"" .ds EE \\h'|10n'
       +.        el .ds EE \\h'\\$3'
       +.        ds EL \\$2\}
       +.if \\n(YE .nf
       +.di EZ
       +..
       +.                \" EN - end of equation
       +.de EN 
       +.br
       +.di
       +.rm EZ
       +.nr ZN \\n(dn
       +.if \\n(ZN .if !\\n(YE .LP
       +.if !\\n(ZN .if !"\\*(EL"" .nr ZN 1
       +.if \\n(ZN \{\
       +.        SP .5v EQ
       +.        X "US EQ"\}
       +'pc
       +.if \\n(BD .nr LE 0 \" don't center if block display or mark/lineup
       +.if \\n(MK \{\
       +.        if \\n(LE=1 .ds EE \\h'|10n'
       +.        nr LE 0\}
       +'lt \\n(.lu
       +.if !\\n(EP .if \\n(ZN \{\
       +.        if \\n(LE=1 .tl \(ts\(ts\\*(10\(ts\\*(EL\(ts
       +.        if \\n(LE=2 .tl \(ts\(ts\(ts\\*(10\\*(EL\(ts
       +.        if !\\n(LE \{\
       +.                if !\\n(BD .tl \(ts\\*(EE\\*(10\(ts\(ts\\*(EL\(ts
       +.                if \\n(BD .if \\n(BD<\\w\(ts\\*(10\(ts .nr BD \\w\(ts\\*(10\(ts
       +.                if \\n(BD \!\\*(10\\t\\*(EL\}\}
       +.if \\n(EP .if \\n(ZN \{\
       +.        if \\n(LE=1 .tl \(ts\\*(EL\(ts\\*(10\(ts\(ts
       +.        if \\n(LE=2 .tl \(ts\\*(EL\(ts\(ts\\*(10\(ts
       +.        if !\\n(LE \{\
       +.                if !\\n(BD .tl \(ts\\*(EL\\*(EE\\*(10\(ts\(ts\(ts
       +.                if \\n(BD .if \\n(BD<\\w\(ts\\*(10\(ts .nr BD \\w\(ts\\*(10\(ts
       +.                if \\n(BD \!\\h'-\\\\n(.iu'\\*(EL\\h'|0'\\*(10\}\}
       +'lt \\n(LLu
       +'pc %
       +.if \\n(YE .if \\n(EF .fi
       +.if \\n(ZN .X "END US EQ"
       +.if \\n(ZN .SP .5v EN
       +.if \\n(ZN .bp
       +..
       +.                \" PS - start picture
       +.de PS                        \" $1 is height, $2 is width, in inches
       +.br
       +.nr X 0.35v
       +.if \\$1>0 .X "SP \\nX PS"
       +.ie \\$1>0 .nr $1 \\$1
       +.el .nr $1 0
       +.X "US PS \\$1
       +.in (\\n(.lu-\\$2)/2u
       +..
       +.                \" PE - end of picture
       +.de PE
       +.in
       +.X "END US PE
       +.nr X .65v
       +.if \\n($1>0 .X "SP \\nX PE"
       +.bp
       +..
       +.de IS        \" for -mpm only
       +.KS
       +..
       +.de IE
       +.KE
       +.bp
       +..
       +.                \" NP - new page
       +.de NP
       +.ev 2
       +.bp
       +.if \\n(KF=0 \{\
       +.        nr PX \\n(.s
       +.        nr PF \\n(.f
       +.        nr PV \\n(.v
       +.        lt \\n(LTu
       +.        ps \\n(PS
       +.        vs \\n(PS+2
       +.        ft 1
       +.        if \\n(PO .po \\n(POu        \" why isn't this reset???
       +.        PT \\$1
       +.        bp
       +.        rs
       +.        BT
       +.        bp
       +.        nr %# +1
       +.        ps \\n(PX
       +.        vs \\n(PVu
       +.        ft \\n(PF \}
       +.ev
       +..
       +.
       +.ds %e .tl '\\*(LH'\\*(CH'\\*(RH'
       +.ds %o .tl '\\*(LH'\\*(CH'\\*(RH'
       +.ds %E .tl '\\*(LF'\\*(CF'\\*(RF'
       +.ds %O .tl '\\*(LF'\\*(CF'\\*(RF'
       +.
       +.                \" PT - page title
       +.de PT
       +.nr PN \\n(%#
       +.X "PT \\n(%#
       +.sp \\n(HMu/2u
       +.if \\n(OL .lt \\n(OLu                \" why isn't this reset???
       +.if \\n(BT>0 .if \\n(%#%2 \\*(%o
       +.if \\n(BT>0 .if !\\n(%#%2 \\*(%e
       +.if \\n(BT=0 .tl '\0'''                \" put out something or spacing is curdled
       +.X "END PT \\n(%#
       +..
       +.                \" BT - bottom title
       +.de BT
       +.X "BT \\n(%#
       +.sp |\\n(FMu/2u+\\n(FOu-1v
       +.if \\n(%#%2 \\*(%O
       +.if !\\n(%#%2 \\*(%E
       +.nr BT \\n(BT+1
       +.X "END BT \\n(%#
       +..
       +.                \" KS - non-floating keep
       +.de KS
       +.br
       +.if "\\n(.z"" .NP  \" defends poorly against including ht of page stuff in diversion for .B1
       +.X "US KS 0
       +.nr KS +1
       +.SP \\n(Ksu
       +..
       +.                \" KF - floating keep
       +.de KF
       +.ev 1
       +.br
       +.if \\n(KS>0 .tm KF won't work inside KS, line \\n(.c, file \\n(.F
       +.if \\n(KF>0 .tm KF won't work inside KF, line \\n(.c, file \\n(.F
       +.nr KF 1
       +.nr 10 0
       +.        if !'\\$1'' .nr 10 \\$1u
       +.        if '\\$1'bottom' .nr 10 \\n(FOu-1u
       +.        if '\\$1'top' .nr 10 \\n(HM
       +.        if \\n(10 .X "UF \\n(10 KF"
       +.        if !\\n(10 .X "UF \\n(HM KF"
       +.        nr X \\n(FOu-2u
       +.        if \\n(10 .X "UF \\n(10 KF"
       +.        if !\\n(10 .X "UF \\nX KF"
       +.nr SJ \\n(.u
       +.ps \\n(PS
       +.if \\n(VS>40 .vs \\n(VSu
       +.if \\n(VS<=39 .vs \\n(VSp
       +.ll \\n(LLu
       +.lt \\n(LTu
       +.SP \\n(Kfu
       +..
       +.                \" KE - end of KS/KF
       +.de KE
       +.bp
       +.ie \\n(KS>0 \{\
       +.        SP \\n(Ksu
       +.        X "END US KS
       +.        nr KS -1 \}
       +.el .ie \\n(KF>0 \{\
       +.        SP \\n(Kfu
       +.        nr KF 0
       +.        X "END UF KF"
       +.        if \\n(SJ .fi
       +.        ev \}
       +.el .tm .KE without preceding .KS or .KF, line \\n(.c, file \\n(.F
       +..
       +.
       +.                \" DS - display. .DS C center; L left-adjust; I indent (default)
       +.de DS                \"  $2 = amount of indent
       +.KS
       +.nf
       +.\\$1D \\$2 \\$1
       +.ft 1
       +.if !\\n(IF \{\
       +.        ps \\n(PS
       +.        if \\n(VS>40 .vs \\n(VSu
       +.        if \\n(VS<=39 .vs \\n(VSp\}
       +..
       +.de D
       +.ID \\$1
       +..
       +.de CD
       +.XD
       +.ce 1000
       +..
       +.de ID
       +.XD
       +.if \\n(.$=0 .in +\\n(DIu
       +.if \\n(.$=1 .if "\\$1"I" .in +\\n(DIu
       +.if \\n(.$=1 .if !"\\$1"I" .in +\\$1n
       +.if \\n(.$>1 .in +\\$2n
       +.....in +0.5i
       +.....if \\n(.$ .if !"\\$1"I" .if !"\\$1"" .in \\n(DIu
       +.....if \\n(.$ .if !"\\$1"I" .if !"\\$1"" .in +\\$1n
       +..
       +.de LD
       +.XD
       +..
       +.de XD
       +.nf
       +.nr OI \\n(.i
       +.SP \\n(DVu
       +..
       +.                \" BD - block display: save everything, then center it.
       +.de BD
       +.XD
       +.nr BD 1
       +.nf
       +.in \\n(OIu
       +.di DD
       +..
       +.                \" DE - display end
       +.de DE
       +.ce 0
       +.if \\n(BD>0 .XF
       +.nr BD 0
       +.in \\n(OIu
       +.SP \\n(DVu
       +.KE
       +.fi
       +..
       +.                \" XF - finish a block display to be recentered.
       +.de XF
       +.di
       +.if \\n(dl>\\n(BD .nr BD \\n(dl
       +.if \\n(BD<\\n(.l .in (\\n(.lu-\\n(BDu)/2u
       +.nr EI \\n(.l-\\n(.i
       +.ta \\n(EIuR
       +.nf
       +.DD
       +.in \\n(OIu
       +..
       +.
       +.
       +.                \" SH - (unnumbered) section heading
       +.de SH
       +.RT
       +.nr X 1v
       +.nr Y 3v
       +.if \\n(1T .NP
       +.if \\n(1T .X "NE \\nY SH"        \" should these be reversed, change Y to 4v
       +.if \\n(1T .X "SP \\nX SH
       +.ft 3
       +..
       +.                \" NH - numbered heading
       +.de NH
       +.RT
       +.nr X 1v
       +.nr Y 3v
       +.if \\n(1T .NP
       +.if \\n(1T .X "NE \\nY NH"        \" should these be reversed, change Y to 4v
       +.if \\n(1T .X "SP \\nX NH
       +.ft 3
       +.nr NS \\$1
       +.if !\\n(.$ .nr NS 1
       +.if !\\n(NS .nr NS 1
       +.nr H\\n(NS +1
       +.if !\\n(NS-4 .nr H5 0
       +.if !\\n(NS-3 .nr H4 0
       +.if !\\n(NS-2 .nr H3 0
       +.if !\\n(NS-1 .nr H2 0
       +.if !\\$1 .if \\n(.$ .nr H1 1
       +.ds SN \\n(H1.
       +.if \\n(NS-1 .as SN \\n(H2.
       +.if \\n(NS-2 .as SN \\n(H3.
       +.if \\n(NS-3 .as SN \\n(H4.
       +.if \\n(NS-4 .as SN \\n(H5.
       +\\*(SN
       +..
       +.                \" RT - reset at beginning of each PP, LP, etc.
       +.de RT
       +.if !\\n(AB .if !\\n(1T .BG
       +.ce 0
       +.if !\\n(AB .if !\\n(KF .if !\\n(IF .if !\\n(IX .if !\\n(BE .di
       +.if \\n(QP \{\
       +.        ll +\\n(QIu
       +.        in -\\n(QIu
       +.        nr QP -1\}
       +.if !\\n(AB \{\
       +.        ll \\n(LLu\}
       +.if !\\n(IF .if !\\n(AB \{\
       +.        ps \\n(PS
       +.        ie \\n(VS>=41 .vs \\n(VSu
       +.        el .vs \\n(VSp\}
       +.ie \\n(IP \{\
       +.        in \\n(I\\n(IRu
       +.        nr IP -1\}
       +.el .if !\\n(IR \{\
       +.        nr I1 \\n(PIu
       +.        nr I2 0
       +.        nr I3 0
       +.        nr I4 0
       +.        nr I5 0\}
       +.if !\\n(AB .ft 1
       +.ta 5n 10n 15n 20n 25n 30n 35n 40n 45n 50n 55n 60n 65n 70n 75n 80n
       +.fi
       +..
       +.                \" BG - begin, execute at first TL, AB, NH, SH, PP, etc.
       +.de BG                \"        IZ has been called, so registers have some value
       +.br
       +.if \\n(CW>0 .if \\n(LL=0 .nr LL \\n(CW+\\n(CW+\\n(GW
       +.ll \\n(LLu
       +.lt \\n(LLu
       +.po \\n(POu
       +.nr YE 1                \" ok to cause break in .EQ (earlier ones won't)
       +.ev 0
       +.hy 14
       +.ev
       +.ev 1
       +.hy 14
       +.ev
       +.ev 2
       +.hy 14
       +.ev
       +.nr 1T 1
       +.X "PARM NP \\n(HM
       +.X "PARM FO \\n(FO
       +.if !\\n(%# .nr %# 1
       +..
       +.                \" PP - paragraph
       +.de PP
       +.RT
       +.if \\n(1T .NP
       +.if \\n(1T .X "SP \\n(PD PP"
       +.if \\n(1T .X "BS 2 PP"
       +.ti +\\n(PIu
       +..
       +.                \" LP - left aligned paragraph
       +.de LP
       +.RT
       +.if \\n(1T .NP
       +.if \\n(1T .X "SP \\n(PD LP"
       +.if \\n(1T .X "BS 2 LP"
       +..
       +.                \" IP - indented paragraph
       +.de IP
       +.RT
       +.if !\\n(IP .nr IP +1
       +.if \\n(1T .NP
       +.if \\n(1T .X "SP \\n(PD PP"
       +.if \\n(1T .X "BS 2 IP"
       +.nr IU \\n(IR+1
       +.if \\n(.$>1 .nr I\\n(IU \\$2n+\\n(I\\n(IRu
       +.if \\n(I\\n(IU=0 .nr I\\n(IU \\n(PIu+\\n(I\\n(IRu
       +.in \\n(I\\n(IUu
       +.nr TY \\n(TZ-\\n(.i
       +.nr JQ \\n(I\\n(IU-\\n(I\\n(IR
       +.ta \\n(JQu \\n(TYuR
       +.if \\n(.$ \{\
       +.ti \\n(I\\n(IRu
       +\&\\$1\t\c\}
       +..
       +.                \" QP - quoted paragraph (within IP)
       +.de QP
       +.RT
       +.if \\n(1T .NP
       +.if \\n(1T .X "SP \\n(PD QP"
       +.if \\n(1T .X "BS 2 QP"
       +.nr QP 1
       +.in +\\n(QIu
       +.ll -\\n(QIu
       +.ti \\n(.iu
       +..
       +.                \" RS - prepare for double indenting
       +.de RS
       +.nr IS \\n(IP
       +.RT
       +.nr IP \\n(IS
       +.nr IU \\n(IR
       +.nr IR +1
       +.if !\\n(I\\n(IR .nr I\\n(IR \\n(I\\n(IU+\\n(PIu
       +.in \\n(I\\n(IRu
       +.nr TY \\n(TZ-\\n(.i
       +.ta \\n(TYuR
       +..
       +.                \" RE - retreat to the left
       +.de RE
       +.nr IS \\n(IP
       +.RT
       +.nr IP \\n(IS
       +.if \\n(IR>0 .nr IR -1
       +.in \\n(I\\n(IRu
       +..
       +.                \" B - bold font
       +.de B
       +.nr PQ \\n(.f
       +.ft 3
       +.if \\n(.$ \&\\$1\\f\\n(PQ\\$2
       +..
       +.                \" BI - bold italic
       +.de BI
       +.nr PQ \\n(.f
       +.ft 4
       +.if \\n(.$ \&\\$1\\f\\n(PQ\\$2
       +..
       +.                \" R - Roman font
       +.de R
       +.nr PQ \\n(.f
       +.ft 1
       +.if \\n(.$ \&\\$1\f\\n(PQ\\$2
       +..
       +.                \" I - italic font
       +.de I
       +.nr PQ \\n(.f
       +.ft 2
       +.if \\n(.$ \&\\$1\^\f\\n(PQ\\$2
       +..
       +.                \" CW - constant width font from -ms
       +.de CW
       +.nr PQ \\n(.f
       +.if \\n(.$=0 .ft CW
       +.if \\n(.$>0 \%\&\\$3\f(CW\\$1\\f\\n(PQ\\$2
       +..
       +.de IT                \" ditto to italicize argument
       +.nr Sf \\n(.f
       +\%\&\\$3\f2\\$1\f\\n(Sf\&\\$2
       +..
       +.                \" TA - tabs set in ens or chars
       +.de TA
       +.ta \\$1n \\$2n \\$3n \\$4n \\$5n \\$6n \\$7n \\$8n \\$9n
       +..
       +.                \" SM - make smaller size
       +.de SM
       +.ie \\n(.$ \&\\$3\s-2\\$1\s0\\$2
       +.el .ps -2
       +..
       +.                \" LG - make larger size
       +.de LG
       +.ie \\n(.$ \&\\$3\s+2\\$1\s0\\$2
       +.el .ps +2
       +..
       +.                \" NL - return to normal size
       +.de NL
       +.ps \\n(PS
       +..
       +.                \" FS - begin footnote
       +.de FS
       +.if \\n(IF>0 .tm .FS within .FS/.FE, line \\n(.c, file \\n(.F
       +.if \\n(KF>0 .tm .FS won't work inside .KF, line \\n(.c, file \\n(.F
       +.if \\n(KS>0 .tm .FS won't work inside .KS, line \\n(.c, file \\n(.F
       +.nr IF 1
       +.ev 1
       +.ps \\n(PS-2
       +.ie \\n(VS>=41 .vs \\n(VSu-2p
       +.el .vs \\n(VSp-2p
       +.ll \\n(LLu
       +.br
       +.nr X \\n(FOu
       +.X "BF \\nX FS
       +.SP .3v
       +....FA        \" deleted by authority of cvw, 10/17/88
       +..
       +.                \" FE - end footnote
       +.de FE
       +.if !\\n(IF .tm .FE without .FS, line \\n(.c, file \\n(.F
       +.br
       +.X "END BF FE
       +.bp
       +.ev
       +.nr IF 0
       +..
       +.                \" FA - the line for a footnote
       +.de FA
       +\l'1i'
       +.br
       +..
       +.                \" Tm - message to be passed on
       +.de Tm
       +.ev 2
       +.if \\n(.$=1 .X "TM \\$1
       +.if \\n(.$=2 .X "TM \\$1 \\$2
       +.if \\n(.$=3 .X "TM \\$1 \\$2 \\$3
       +.if \\n(.$=4 .X "TM \\$1 \\$2 \\$3 \\$4
       +.if \\n(.$=5 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5
       +.if \\n(.$=6 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5 \\$6
       +.if \\n(.$=7 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7
       +.if \\n(.$=8 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8
       +.if \\n(.$=9 .X "TM \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
       +.br
       +.ev
       +..
       +.de MH
       +AT&T Bell Laboratories
       +Murray Hill, New Jersey 07974
       +..
       +.de HO
       +AT&T Bell Laboratories
       +Holmdel, New Jersey 07733
       +..
       +.de WH
       +AT&T Bell Laboratories
       +Whippany, New Jersey 07981
       +..
       +.de IH
       +AT&T Bell Laboratories
       +Naperville, Illinois 60540
       +..
       +.                \" UL - underline argument, don't italicize
       +.de UL
       +\\$1\l'|0\(ul'\\$2
       +..
       +.                \" UX - print $2 UNIX $1
       +.de UX
       +.ie \\n(UX \\$2\s-1UNIX\s0\\$1
       +.el \{\
       +\\$2\s-1UNIX\\s0\\$1\(rg
       +.nr UX 1\}
       +..
       +.                \" QS - start quote
       +.de QS
       +.br
       +.LP
       +.in +\\n(QIu
       +.ll -\\n(QIu
       +..
       +.                \" QE - end quote
       +.de QE
       +.br
       +.ll +\\n(QIu
       +.in -\\n(QIu
       +.LP
       +..
       +.                \"  B1 - begin boxed stuff
       +.de B1
       +.br
       +.di BB
       +.nr BC 0
       +.if "\\$1"C" .nr BC 1
       +.nr BE 1
       +..
       +.                \" B2 - end boxed stuff
       +.de B2 
       +.br
       +.nr BI 1n
       +.if \\n(.$>0 .nr BI \\$1n
       +.di
       +.nr BE 0
       +.nr BW \\n(dl
       +.nr BH \\n(dn
       +.ne \\n(BHu+\\n(.Vu
       +.nr BQ \\n(.j
       +.nf
       +.ti 0
       +.if \\n(BC>0 .in +(\\n(.lu-\\n(BWu)/2u
       +.in +\\n(BIu
       +.ls 1
       +.BB
       +.ls
       +.in -\\n(BIu
       +.nr BW +2*\\n(BI
       +.sp -1
       +\l'\\n(BWu\(ul'\L'-\\n(BHu'\l'|0\(ul'\h'|0'\L'\\n(BHu'
       +.if \\n(BC>0 .in -(\\n(.lu-\\n(BWu)/2u
       +.if \\n(BQ .fi
       +.br
       +..
       +.                \" BX - boxed stuff
       +.de BX
       +\(br\|\\$1\|\(br\l'|0\(rn'\l'|0\(ul'
       +..
       +.
       +.        \" macros for programs, etc.
       +.
       +.ig
       +        programs are displayed between .P1/.P2 pairs
       +        default is to indent by 1/2 inch, nofill, dP smaller
       +        .P1 x causes an indent of x instead.
       +
       +        .P3 can be used to specify optional page-break points
       +        inside .P1/.P2
       +..
       +.
       +.                \" P1 - start of program
       +.de P1
       +.nr $1 \\n(P1
       +.if \\n(.$ .nr $1 \\$1n
       +.br
       +.X "SP \\n(DV P1"
       +.X "US P1"
       +.in \\n($1u
       +.nf
       +.nr v \\n(.v
       +.ps -\\n(dP
       +.vs -\\n(dVu
       +.ft CW
       +.nr t \\n(dT*\\w'x'u
       +.ta 1u*\\ntu 2u*\\ntu 3u*\\ntu 4u*\\ntu 5u*\\ntu 6u*\\ntu 7u*\\ntu 8u*\\ntu 9u*\\ntu 10u*\\ntu 11u*\\ntu 12u*\\ntu 13u*\\ntu 14u*\\ntu
       +..
       +.                \" P2 - end of program
       +.de P2
       +.br
       +.ps \\n(PS
       +.vs \\nvu
       +.ft 1
       +.in
       +.X "END US P1
       +.X "SP \\n(DV P2"
       +.fi
       +..
       +.                \" P3 - provides optional unpadded break in P1/P2
       +.de P3
       +.nr x \\n(DV
       +.nr DV 0
       +.P2
       +.P1 \\n($1u
       +.nr DV \\nx
       +..
       +.de [
       +[
       +..
       +.de ]
       +]
       +..
       +.IZ
       +.rm IZ
       +.so /sys/lib/tmac/tmac.srefs
   DIR diff --git a/src/cmd/pic/arcgen.c b/src/cmd/pic/arcgen.c
       t@@ -11,17 +11,19 @@ obj *arcgen(int type)        /* handles circular and (eventually) elliptical arcs */
                static double prevw = HT10;
                static double prevh = HT5;
                static double prevrad = HT2;
       -        static int dtox[2][4] ={ 1, -1, -1, 1, 1, 1, -1, -1 };
       -        static int dtoy[2][4] ={ 1, 1, -1, -1, -1, 1, 1, -1 };
       -        static int dctrx[2][4] ={ 0, -1, 0, 1, 0, 1, 0, -1 };
       -        static int dctry[2][4] ={ 1, 0, -1, 0, -1, 0, 1, 0 };
       -        static int nexthv[2][4] ={ U_DIR, L_DIR, D_DIR, R_DIR, D_DIR, R_DIR, U_DIR, L_DIR };
       +        static int dtox[2][4] ={ { 1, -1, -1, 1}, {1, 1, -1, -1} };
       +        static int dtoy[2][4] ={ {1, 1, -1, -1}, {-1, 1, 1, -1} };
       +        static int dctrx[2][4] ={ {0, -1, 0, 1}, {0, 1, 0, -1} };
       +        static int dctry[2][4] ={ {1, 0, -1, 0}, {-1, 0, 1, 0} };
       +        static int nexthv[2][4] ={ {U_DIR, L_DIR, D_DIR, R_DIR}, {D_DIR, R_DIR, U_DIR, L_DIR} };
                double dx2, dy2, ht, phi, r, d;
                int i, head, to, at, cw, invis, ddtype, battr;
                obj *p, *ppos;
                double fromx, fromy, tox, toy, fillval = 0;
                Attr *ap;
        
       +        tox=toy=0.0; /* Botch? (gcc) */
       +
                prevrad = getfval("arcrad");
                prevh = getfval("arrowht");
                prevw = getfval("arrowwid");
       t@@ -210,6 +212,7 @@ void arc_extreme(double x0, double y0, double x1, double y1, double xc, double y
                extreme(xmax, ymax);
        }
        
       +int
        quadrant(double x, double y)
        {
                if (     x>=0.0 && y> 0.0) return(1);
   DIR diff --git a/src/cmd/pic/circgen.c b/src/cmd/pic/circgen.c
       t@@ -12,6 +12,8 @@ obj *circgen(int type)
                obj *p, *ppos;
                Attr *ap;
        
       +        r = r2 = 0.0; /* Botch? (gcc) */
       +
                battr = at = 0;
                with = xwith = ywith = fillval = ddval = 0;
                t = (type == CIRCLE) ? 0 : 1;
   DIR diff --git a/src/cmd/pic/input.c b/src/cmd/pic/input.c
       t@@ -26,7 +26,7 @@ void pushsrc(int type, char *ptr)        /* new input source */
                srcp->type = type;
                srcp->sp = ptr;
                if (dbg > 1) {
       -                printf("\n%3d ", srcp - src);
       +                printf("\n%3d ", (int) (srcp - src));
                        switch (srcp->type) {
                        case File:
                                printf("push file %s\n", ((Infile *)ptr)->fname);
       t@@ -57,7 +57,7 @@ void popsrc(void)        /* restore an old one */
                if (srcp <= src)
                        ERROR "too many inputs popped" FATAL;
                if (dbg > 1) {
       -                printf("%3d ", srcp - src);
       +                printf("%3d ", (int) (srcp - src));
                        switch (srcp->type) {
                        case File:
                                printf("pop file\n");
       t@@ -142,6 +142,7 @@ char *delimstr(char *s)        /* get body of X ... X */
                return tostring(buf);
        }
        
       +int
        baldelim(int c, char *s)        /* replace c by balancing entry in s */
        {
                for ( ; *s; s += 2)
       t@@ -187,11 +188,12 @@ void dodef(struct symtab *stp)        /* collect args and switch input to defn */
                        ap->argstk[i] = "";
                if (dbg)
                        for (i = 0; i < argcnt; i++)
       -                        printf("arg %d.%d = <%s>\n", ap-args, i+1, ap->argstk[i]);
       +                        printf("arg %d.%d = <%s>\n", (int) (ap-args), i+1, ap->argstk[i]);
                argfp = ap;
                pushsrc(Macro, stp->s_val.p);
        }
        
       +int
        getarg(char *p)        /* pick up single argument, store in p, return length */
        {
                int n, c, npar;
       t@@ -232,6 +234,7 @@ extern        int        thru;
        extern        struct symtab        *thrudef;
        extern        char        *untilstr;
        
       +int
        input(void)
        {
                register int c;
       t@@ -248,10 +251,13 @@ input(void)
                return *ep++ = c;
        }
        
       +int
        nextchar(void)
        {
                register int c;
        
       +        c = 0; /* Botch: gcc */
       +
          loop:
                switch (srcp->type) {
                case Free:        /* free string */
       t@@ -289,9 +295,9 @@ nextchar(void)
                                        ERROR "argfp underflow" FATAL;
                                popsrc();
                                goto loop;
       -                } else if (c == '$' && isdigit(*srcp->sp)) {
       +                } else if (c == '$' && isdigit((unsigned char) *srcp->sp)) {
                                int n = 0;
       -                        while (isdigit(*srcp->sp))
       +                        while (isdigit((unsigned char) *srcp->sp))
                                        n = 10 * n + *srcp->sp++ - '0';
                                if (n > 0 && n <= MAXARGS)
                                        pushsrc(String, argfp->argstk[n-1]);
       t@@ -380,7 +386,7 @@ void do_thru(void)        /* read one line, make into a macro expansion */
                        ap->argstk[i] = "";
                if (dbg)
                        for (i = 0; i < argcnt; i++)
       -                        printf("arg %d.%d = <%s>\n", ap-args, i+1, ap->argstk[i]);
       +                        printf("arg %d.%d = <%s>\n", (int) (ap-args), i+1, ap->argstk[i]);
                if (strcmp(ap->argstk[0], ".PE") == 0) {
                        thru = 0;
                        thrudef = 0;
       t@@ -400,6 +406,7 @@ void do_thru(void)        /* read one line, make into a macro expansion */
                pushsrc(Macro, thrudef->s_val.p);
        }
        
       +int
        unput(int c)
        {
                if (++pb >= pbuf + sizeof pbuf)
       t@@ -580,7 +587,7 @@ void shell_init(void)        /* set up to interpret a shell command */
        
        void shell_text(char *s)        /* add string to command being collected */
        {
       -        while (*shellp++ = *s++)
       +        while ((*shellp++ = *s++))
                        ;
                shellp--;
        }
   DIR diff --git a/src/cmd/pic/main.c b/src/cmd/pic/main.c
       t@@ -47,6 +47,7 @@ void        getdata(void), setdefaults(void);
        void        setfval(char *, double);
        int        getpid(void);
        
       +int
        main(int argc, char *argv[])
        {
                char buf[20];
       t@@ -120,27 +121,27 @@ static struct {
                double val;
                short scalable;                /* 1 => adjust when "scale" changes */
        } defaults[] ={
       -        "scale", SCALE, 1,
       -        "lineht", HT, 1,
       -        "linewid", HT, 1,
       -        "moveht", HT, 1,
       -        "movewid", HT, 1,
       -        "dashwid", HT10, 1,
       -        "boxht", HT, 1,
       -        "boxwid", WID, 1,
       -        "circlerad", HT2, 1,
       -        "arcrad", HT2, 1,
       -        "ellipseht", HT, 1,
       -        "ellipsewid", WID, 1,
       -        "arrowht", HT5, 1,
       -        "arrowwid", HT10, 1,
       -        "arrowhead", 2, 0,                /* arrowhead style */
       -        "textht", 0.0, 1,                /* 6 lines/inch is also a useful value */
       -        "textwid", 0.0, 1,
       -        "maxpsht", MAXHT, 0,
       -        "maxpswid", MAXWID, 0,
       -        "fillval", 0.7, 0,                /* gray value for filling boxes */
       -        NULL, 0, 0
       +        { "scale", SCALE, 1, },
       +        { "lineht", HT, 1, },
       +        { "linewid", HT, 1, },
       +        { "moveht", HT, 1, },
       +        { "movewid", HT, 1, },
       +        { "dashwid", HT10, 1, },
       +        { "boxht", HT, 1, },
       +        { "boxwid", WID, 1, },
       +        { "circlerad", HT2, 1, },
       +        { "arcrad", HT2, 1, },
       +        { "ellipseht", HT, 1, },
       +        { "ellipsewid", WID, 1, },
       +        { "arrowht", HT5, 1, },
       +        { "arrowwid", HT10, 1, },
       +        { "arrowhead", 2, 0, },                /* arrowhead style */
       +        { "textht", 0.0, 1, },                /* 6 lines/inch is also a useful value */
       +        { "textwid", 0.0, 1, },
       +        { "maxpsht", MAXHT, 0, },
       +        { "maxpswid", MAXWID, 0, },
       +        { "fillval", 0.7, 0, },                /* gray value for filling boxes */
       +        { NULL, 0, 0 }
        };
        
        void setdefaults(void)        /* set default sizes for variables like boxht */
   DIR diff --git a/src/cmd/pic/misc.c b/src/cmd/pic/misc.c
       t@@ -9,6 +9,7 @@ int whatpos(obj *p, int corner, double *px, double *py);
        void makeattr(int type, int sub, YYSTYPE val);
        YYSTYPE getblk(obj *, char *);
        
       +int
        setdir(int n)        /* set direction (hvmode) from LEFT, RIGHT, etc. */
        {
                switch (n) {
       t@@ -20,6 +21,7 @@ setdir(int n)        /* set direction (hvmode) from LEFT, RIGHT, etc. */
                 return(hvmode);
        }
        
       +int
        curdir(void)        /* convert current dir (hvmode) to RIGHT, LEFT, etc. */
        {
                switch (hvmode) {
       t@@ -32,7 +34,8 @@ curdir(void)        /* convert current dir (hvmode) to RIGHT, LEFT, etc. */
                return 0;
        }
        
       -double getcomp(obj *p, int t)        /* return component of a position */
       +double 
       +getcomp(obj *p, int t)        /* return component of a position */
        {
                switch (t) {
                case DOTX:
       t@@ -207,7 +210,9 @@ int whatpos(obj *p, int corner, double *px, double *py)        /* what is the position 
        {
                double x, y, x1, y1;
        
       -        dprintf("whatpos %o %d %d\n", p, p->o_type, corner);
       +        x1 = y1 = 0.0; /* Botch? (gcc) */
       +
       +        dprintf("whatpos %p %d %d\n", p, p->o_type, corner);
                x = p->o_x;
                y = p->o_y;
                if (p->o_type != PLACE && p->o_type != MOVE) {
       t@@ -320,7 +325,7 @@ obj *getlast(int n, int t)        /* find n-th previous occurrence of type t */
                        dprintf("got a last of x,y= %g,%g\n", p->o_x, p->o_y);
                        return(p);
                }
       -        ERROR "there is no %dth last", n FATAL;
       +        ERROR "there is no %dth last", n WARNING;
                return(NULL);
        }
        
       t@@ -343,7 +348,7 @@ obj *getfirst(int n, int t)        /* find n-th occurrence of type t */
                        dprintf("got a first of x,y= %g,%g\n", p->o_x, p->o_y);
                        return(p);
                }
       -        ERROR "there is no %dth ", n FATAL;
       +        ERROR "there is no %dth ", n WARNING;
                return(NULL);
        }
        
   DIR diff --git a/src/cmd/pic/picl.lx b/src/cmd/pic/picl.lx
       t@@ -133,7 +133,7 @@ WS        [ \t]
        <A>ccw                { yylval.i = CCW; return(ATTR); }
        <A>invis(ible)?        { yylval.i = INVIS; return(ATTR); }
        <A>noedge        { yylval.i = INVIS; return ATTR; }
       -<A>fill                return(yylval.i = FILL);
       +<A>fill                { yylval.i = FILL; return ATTR; }
        <A>solid        ;
        <A>dot(ted)?        return(yylval.i = DOT);
        <A>dash(ed)?        return(yylval.i = DASH);
   DIR diff --git a/src/cmd/pic/print.c b/src/cmd/pic/print.c
       t@@ -25,6 +25,8 @@ void print(void)
                int fill, vis, invis;
                double x0, y0, x1, y1, ox, oy, dx, dy, ndx, ndy;
        
       +        x1 = y1 = 0.0; /* Botch? (gcc) */
       +
                for (i = 0; i < nobj; i++) {
                        p = objlist[i];
                        ox = p->o_x;
       t@@ -180,6 +182,8 @@ void dotline(double x0, double y0, double x1, double y1, int ddtype, double ddva
                int i, numdots;
                double a, b, dx, dy;
        
       +        b = 0.0; /* Botch? (gcc) */
       +
                if (ddval == 0)
                        ddval = prevval;
                prevval = ddval;
   DIR diff --git a/src/cmd/pic/symtab.c b/src/cmd/pic/symtab.c
       t@@ -12,7 +12,7 @@ YYSTYPE getvar(char *s)        /* return value of variable s (usually pointer) */
        
                p = lookup(s);
                if (p == NULL) {
       -                if (islower(s[0]))
       +                if (islower((int) s[0]))
                                ERROR "no such variable as %s", s WARNING;
                        else
                                ERROR "no such place as %s", s WARNING;