home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Professional / OS2PRO194.ISO / os2 / fileutil / cawf / pass2.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-04-26  |  26.9 KB  |  1,187 lines

  1. /*
  2.  *    pass2.c - cawf(1) pass 2 function
  3.  */
  4.  
  5. /*
  6.  *    Copyright (c) 1991 Purdue University Research Foundation,
  7.  *    West Lafayette, Indiana 47907.  All rights reserved.
  8.  *
  9.  *    Written by Victor A. Abell <abe@mace.cc.purdue.edu>,  Purdue
  10.  *    University Computing Center.  Not derived from licensed software;
  11.  *    derived from awf(1) by Henry Spencer of the University of Toronto.
  12.  *
  13.  *    Permission is granted to anyone to use this software for any
  14.  *    purpose on any computer system, and to alter it and redistribute
  15.  *    it freely, subject to the following restrictions:
  16.  *
  17.  *    1. The author is not responsible for any consequences of use of
  18.  *       this software, even if they arise from flaws in it.
  19.  *
  20.  *    2. The origin of this software must not be misrepresented, either
  21.  *       by explicit claim or by omission.  Credits must appear in the
  22.  *       documentation.
  23.  *
  24.  *    3. Altered versions must be plainly marked as such, and must not
  25.  *       be misrepresented as being the original software.  Credits must
  26.  *       appear in the documentation.
  27.  *
  28.  *    4. This notice may not be removed or altered.
  29.  */
  30.  
  31. #include "cawf.h"
  32. #ifdef    UNIX
  33. #include <strings.h>
  34. #else
  35. #include <string.h>
  36. #endif
  37. #include <ctype.h>
  38.  
  39. /*
  40.  * Pass2(line) - process the nroff commands in a line and break
  41.  *         text into words for pass 3
  42.  */
  43.  
  44. void
  45. Pass2(line)
  46.     char *line;
  47. {
  48.     char buf[MAXLINE];        /* working buffer */
  49.     char c;                /* character buffer */
  50.     double d;            /* temporary double */
  51.     double exscale;            /* expression scaling factor */
  52.     double expr[MAXEXP];            /* expressions */
  53.     char exsign[MAXEXP];            /* expression signs */
  54.     int i, j, k;            /* temporary indexes */
  55.     int inword;            /* word processing status */
  56.     int nexpr;            /* number of expressions */
  57.     char nm[4];            /* name */
  58.     int nsp;            /* number of spaces */
  59.     char op;            /* expression term operator */
  60.     char opstack[MAXSP];        /* expression operation stack */
  61.     char period;            /* end of word status */
  62.     char *s1, *s2, *s3, *s4, *s5;    /* temporary string pointers */
  63.     double sexpr[MAXEXP];           /* signed expressions */
  64.     int sp;                /* expression stack pointer */
  65.     char ssign;            /* expression's starting sign */
  66.     int tabpos;            /* tab position */
  67.     double tscale;            /* term scaling factor */
  68.     double tval;            /* term value */
  69.     double val;            /* term value */
  70.     double valstack[MAXSP];        /* expression value stack */
  71.     char xbuf[MAXLINE];        /* expansion buffer */
  72.  
  73.     if (line == NULL) {
  74.     /*
  75.      * End of macro expansion.
  76.      */
  77.         Pass3(DOBREAK, "need", NULL, 999);    /* flush page */
  78.         Pass3(DOBREAK, "flush", NULL, 0);
  79.         return;
  80.     }
  81.     /*
  82.      * Adjust line number.
  83.      */
  84.     if (Lockil == 0)
  85.         P2il++;
  86.     /*
  87.      * Empty line - "^[ \t]*$".
  88.      */
  89.     if (regexec(Pat[6].pat, line)) {
  90.         Pass3(DOBREAK, "space", NULL, 0);
  91.         return;
  92.     }
  93.     /*
  94.      * Line begins with white space.
  95.      */
  96.     if (*line == ' ' || *line == '\t') {
  97.         Pass3(DOBREAK, "flush", NULL, 0);
  98.         Pass3(0, "", NULL, 0);
  99.     }
  100.     /*
  101.      * Line contains text (not an nroff command).
  102.      */
  103.     if (*line != '.') {
  104.         if (Font[0] == 'R' && Backc == 0 && Aftnxt == NULL
  105.         &&  regexec(Pat[7].pat, line) == 0) {
  106.             /*
  107.              * The font is Roman, there is no "\\c" or "after next"
  108.              * trap pending and and the line has no '\\', '\t', '-',
  109.              * or "  "  (regular expression "\\|\t|-|  ").
  110.              *
  111.              * Output each word of the line as "<length> <word>".
  112.              */
  113.             for (s1 = line;;) {
  114.                 while (*s1 && *s1 == ' ')
  115.                     s1++;
  116.                 if (*s1 == '\0')
  117.                     break;
  118.                 for (s2 = s1, s3 = buf; *s2 && *s2 != ' ';)
  119.                     *s3++ = *s2++;
  120.                 *s3 = '\0';
  121.                 Pass3((s2 - s1), buf, NULL, 0);
  122.                 s1 = *s2 ? ++s2 : s2;
  123.             }
  124.             /*
  125.              * Line terminates with punctuation and optional
  126.              * bracketing (regular expression "[.!?:][\])'\"*]*$").
  127.              */
  128.             if (regexec(Pat[8].pat, line))
  129.                 Pass3(NOBREAK, "gap", NULL, 2);
  130.             if (Centering > 0) {
  131.                 Pass3(DOBREAK,"center", NULL, 0);
  132.                 Centering--;
  133.             } else if (Fill == 0)
  134.                 Pass3(DOBREAK, "flush", NULL, 0);
  135.             return;
  136.         }
  137.         /*
  138.          * Line must be scanned a character at a time.
  139.          */
  140.         inword = nsp = tabpos = 0;
  141.         period = '\0';
  142.         for (s1 = line;; s1++) {
  143.             /*
  144.              * Space or TAB causes state transition.
  145.              */
  146.             if (*s1 == '\0' || *s1 == ' ' || *s1 == '\t') {
  147.                 if (inword) {
  148.                     if (!Backc) {
  149.                         Word[Wordx] = '\0';
  150.                         Pass3(Wordl, Word, NULL, 0);
  151.                         if (Uhyph) {
  152.                             Pass3(NOBREAK, "nohyphen",
  153.                             NULL, 0);
  154.                         }
  155.                     }
  156.                     inword = 0;
  157.                     nsp = 0;
  158.                 }
  159.                 if (*s1 == '\0')
  160.                     break;
  161.             } else {
  162.                 if (inword == 0) {
  163.                     if (Backc == 0) {
  164.                         Wordl = Wordx = 0;
  165.                         Uhyph = 0;
  166.                     }
  167.                     Backc = 0;
  168.                     inword = 1;
  169.                     if (nsp > 1) {
  170.                         Pass3(NOBREAK, "gap", NULL,
  171.                             nsp);
  172.                     }
  173.                 }
  174.             }
  175.             /*
  176.              * Process a character.
  177.              */
  178.             switch (*s1) {
  179.             /*
  180.              * Space
  181.              */
  182.                  case ' ':
  183.                 nsp++;
  184.                 period = '\0';
  185.                 break;
  186.             /*
  187.              * TAB
  188.              */
  189.                  case '\t':
  190.                 tabpos++;
  191.                 if (tabpos <= Ntabs) {
  192.                     Pass3(NOBREAK, "tabto", NULL,
  193.                         Tabs[tabpos-1]);
  194.                 }
  195.                 nsp = 0;
  196.                 period = '\0';
  197.                 break;
  198.             /*
  199.              * Hyphen if word is being assembled
  200.              */
  201.             case '-':
  202.                 if (Wordl <= 0)
  203.                     goto ordinary_char;
  204.                 if ((i = Findhy(NULL, 0, 0)) < 0) {
  205.                     Error(WARN, LINE, " no hyphen for font ",
  206.                     Font);
  207.                     return;
  208.                 }
  209.                 Word[Wordx] = '\0';
  210.                 Pass3(Wordl, Word, NULL, Hychar[i].len);
  211.                 Pass3(NOBREAK, "userhyphen", Hychar[i].str,
  212.                     Hychar[i].len);
  213.                 Wordl = Wordx = 0;
  214.                 period = '\0';
  215.                 Uhyph = 1;
  216.                 break;
  217.             /*
  218.              * Backslash
  219.              */
  220.             case '\\':
  221.                 s1++;
  222.                 switch(*s1) {
  223.                 /*
  224.                  * Change font - "\\fN"
  225.                  */
  226.                 case 'f':
  227.                     s1 = Asmcode(&s1, nm);
  228.                     if (nm[0] == 'P') {
  229.                         Font[0] = Prevfont;
  230.                         break;
  231.                     }
  232.                     for (i = 0; Fcode[i].nm; i++) {
  233.                         if (Fcode[i].nm == nm[0])
  234.                         break;
  235.                     }
  236.                     if (Fcode[i].nm == '\0'
  237.                     ||  nm[1] != '\0') {
  238.                         Error(WARN, LINE, " unknown font ",
  239.                             nm);
  240.                         break;
  241.                     }
  242.                     if (Fcode[i].status != '1') {
  243.                         Error(WARN, LINE,
  244.                         " font undefined ", nm);
  245.                         break;
  246.                     } else {
  247.                         Prevfont = Font[0];
  248.                         Font[0] = nm[0];
  249.                     }
  250.                     break;
  251.                 /*
  252.                  * Positive horizontal motion - "\\h\\n(NN" or
  253.                  * "\\h\\nN"
  254.                  */
  255.                 case 'h':
  256.                     if (s1[1] != '\\' || s1[2] != 'n') {
  257.                         Error(WARN, LINE,
  258.                         " no \\n after \\h", NULL);
  259.                         break;
  260.                     }
  261.                     s1 +=2;
  262.                     s1 = Asmcode(&s1, nm);
  263.                     if ((i = Findnum(nm, 0, 0)) < 0)
  264.                         goto unknown_num;
  265.                     if ((j = Numb[i].val) < 0) {
  266.                         Error(WARN, LINE, " \\h < 0 ",
  267.                         NULL);
  268.                         break;
  269.                     }
  270.                     if (j == 0)
  271.                         break;
  272.                     if ((strlen(s1+1) + j + 1) >=  MAXLINE)
  273.                         goto line_too_long;
  274.                     for (s2 = &xbuf[1]; j; j--)
  275.                         *s2++ = ' ';
  276.                     (void) strcpy(s2, s1+1);
  277.                     s1 = xbuf;
  278.                     break;
  279.                 /*
  280.                  * Interpolate number - "\\n(NN" or "\\nN"
  281.                  */
  282.                 case 'n':
  283.                     s1 = Asmcode(&s1, nm);
  284.                     if ((i = Findnum(nm, 0, 0)) < 0) {
  285. unknown_num:
  286.                         Error(WARN, LINE,
  287.                             " unknown number register ",
  288.                         nm);
  289.                         break;
  290.                     }
  291.                     (void) sprintf(buf, "%d",
  292.                         Numb[i].val);
  293.                     if ((strlen(buf) + strlen(s1+1) + 1)
  294.                     >=  MAXLINE) {
  295. line_too_long:
  296.                         Error(WARN, LINE, " line too long",
  297.                             NULL);
  298.                         break;
  299.                     }
  300.                     (void) sprintf(buf, "%d%s",
  301.                         Numb[i].val, s1+1);
  302.                     (void) strcpy(&xbuf[1], buf);
  303.                         s1 = xbuf;
  304.                     break;
  305.                 /*
  306.                  * Change size - "\\s[+-][0-9]" - NOP
  307.                  */
  308.                 case 's':
  309.                     s1++;
  310.                     if (*s1 == '+' || *s1 == '-')
  311.                         s1++;
  312.                     while (*s1 && isdigit(*s1))
  313.                         s1++;
  314.                     s1--;
  315.                     break;
  316.                 /*
  317.                  * Continue - "\\c"
  318.                  */
  319.                 case 'c':
  320.                     Backc = 1;
  321.                     break;
  322.                 /*
  323.                  * Interpolate string - "\\*(NN" or "\\*N"
  324.                  */
  325.                 case '*':
  326.                     s1 = Asmcode(&s1, nm);
  327.                     s2 = Findstr(nm, NULL, 0);
  328.                     if (*s2 != '\0') {
  329.                         if ((strlen(s2) + strlen(s1+1) + 1)
  330.                         >=  MAXLINE)
  331.                         goto line_too_long;
  332.                         (void) sprintf(buf, "%s%s",
  333.                         s2, s1+1);
  334.                         (void) strcpy(&xbuf[1], buf);
  335.                         s1 = xbuf;
  336.                     }
  337.                     break;
  338.                 /*
  339.                  * Discretionary hyphen - "\\%"
  340.                  */
  341.                 case '%':
  342.                     if (Wordl <= 0)
  343.                         break;
  344.                     if ((i = Findhy(NULL, 0, 0)) < 0) {
  345.                         Error(WARN, LINE,
  346.                             " np hyphen for font ", Font);
  347.                         break;
  348.                     }
  349.                     Word[Wordx] = '\0';
  350.                     Pass3(Wordl, Word, NULL, Hychar[i].len);
  351.                     Pass3(NOBREAK, "hyphen", Hychar[i].str,
  352.                         Hychar[i].len);
  353.                     Wordl = Wordx = 0;
  354.                     Uhyph = 1;
  355.                     break;
  356.                 /*
  357.                  * None of the above - may be special character
  358.                  * name.
  359.                  */
  360.                 default:
  361.                     s2 = --s1;
  362.                     s1 = Asmcode(&s1, nm);
  363.                     if ((i = Findchar(nm, 0, 0, 0)) < 0) {
  364.                         s1 = s2;
  365.                         goto ordinary_char;
  366.                     }
  367.                     if (strcmp(nm, "em") == 0
  368.                     && Wordx > 0) {
  369.                     /*
  370.                      * "\\(em" is a special case when a word
  371.                      * has been assembled, because of
  372.                      * hyphenation.
  373.                      */
  374.                         Word[Wordx] = '\0';
  375.                         Pass3(Wordl, Word, NULL,
  376.                             Schar[i].len);
  377.                         Pass3(NOBREAK, "userhyphen",
  378.                             Schar[i].str, Schar[i].len);
  379.                             Wordl = Wordx = 0;
  380.                         period = '\0';
  381.                         Uhyph = 1;
  382.                      }
  383.                     /*
  384.                      * Interpolate a special character
  385.                      */
  386.                     if ((strlen(Schar[i].str) + Wordx)
  387.                     >= MAXLINE)
  388.                         goto word_too_long;
  389.                         for (s2 = Schar[i].str; *s2; s2++)
  390.                         Word[Wordx++] = *s2;
  391.                         Wordl += Schar[i].len;
  392.                     period = '\0';
  393.                 }
  394.                 break;
  395.             /*
  396.              * Ordinary character
  397.              */
  398.             default:
  399. ordinary_char:
  400.                 if ((Font[0] == 'B' && (Wordx+5) >= MAXLINE)
  401.                 ||  (Font[0] == 'I' && (Wordx+3) >= MAXLINE)) {
  402. word_too_long:
  403.                     Error(WARN, LINE, " word too long",
  404.                         NULL);
  405.                     return;
  406.                 }
  407.                 if (Font[0] == 'B') {
  408.                     Word[Wordx++] = *s1;
  409.                     Word[Wordx++] = '\b';
  410.                     Word[Wordx++] = *s1;
  411.                     Word[Wordx++] = '\b';
  412.                     Word[Wordx++] = *s1;
  413.                 } else if (Font[0] == 'I' && isalnum(*s1)) {
  414.                     Word[Wordx++] = '_';
  415.                     Word[Wordx++] = '\b';
  416.                     Word[Wordx++] = *s1;
  417.                 } else {
  418.                     if ((Wordx+1) >= MAXLINE)
  419.                     goto word_too_long;
  420.                     Word[Wordx++] = *s1;
  421.                 }
  422.                 Wordl++;
  423.                 if (*s1 == '.' || *s1 == '!'
  424.                 ||  *s1 == '?' || *s1 == ':')
  425.                     period = '.';
  426.                 else if (period == '.') {
  427.                     nm[0] = *s1;
  428.                     nm[1] = '\0';
  429.                     if (regexec(Pat[13].pat, nm) == 0)
  430.                      period = '\0';
  431.                 }
  432.             }
  433.         }
  434.         /*
  435.          * End of line processing
  436.          */
  437.              if (!Backc) {
  438.             if (period == '.')
  439.                 Pass3(NOBREAK, "gap", NULL, 2);
  440.             if (Centering > 0) {
  441.                 Pass3(DOBREAK, "center", NULL, 0);
  442.                 Centering--;
  443.             } else if (!Fill)
  444.                 Pass3(DOBREAK, "flush", NULL, 0);
  445.         }
  446.         if (Aftnxt == NULL)
  447.             return;
  448.         /* else fall through to process an "after next trap */
  449.     }
  450.     /*
  451.      * Special -man macro handling.
  452.      */
  453.     if (Marg == MANMACROS) {
  454.         /*
  455.          * A text line - "^[^.]" - is only processed when there is an
  456.          * "after next" directive.
  457.          */
  458.         if (*line != '.') {
  459.             if (Aftnxt != NULL) {
  460.                 if (regexec(Pat[9].pat, Aftnxt))  /* ",fP" */
  461.                     Font[0] = Prevfont;
  462.                 if (regexec(Pat[10].pat, Aftnxt))  /* ",tP" */
  463.                     Pass3(DOBREAK, "toindent", NULL, 0);
  464.                 Free(&Aftnxt);
  465.             }
  466.             return;
  467.         }
  468.         /*
  469.          * Special footer handling - "^.lF"
  470.          */
  471.         if (line[1] == 'l' && line[2] == 'F') {
  472.             s1 = Findstr("by", NULL, 0);
  473.             s2 = Findstr("nb", NULL, 0);
  474.             if (*s1 == '\0' || *s2 == '\0')
  475.                 (void) sprintf(buf, "%s%s", s1, s2);
  476.             else
  477.                 (void) sprintf(buf, "%s; %s", s1, s2);
  478.             Pass3(NOBREAK, "LF", buf, 0);
  479.             return;
  480.         }
  481.     }
  482.     /*
  483.      * Special -ms macro handling.
  484.      */
  485.     if (Marg == MSMACROS) {
  486.         /*
  487.          * A text line - "^[^.]" - is only processed when there is an
  488.          * "after next" directive.
  489.          */
  490.         if (*line != '.') {
  491.             if (Aftnxt != NULL) {
  492.                 if (regexec(Pat[10].pat, Aftnxt))  /* ",tP" */
  493.                     Pass3(DOBREAK, "toindent", NULL, 0);
  494.                 Free(&Aftnxt);
  495.             }
  496.             return;
  497.         }
  498.         /*
  499.          * Numbered headings - "^\.nH"
  500.          */
  501.         if (line[1] == 'n' && line[2] == 'H') {
  502.             s1 = Field(2, line, 0);
  503.             if (s1 != NULL) {
  504.                 i = atoi(s1) - 1;    
  505.                 if (i < 0) {
  506.                     for (j = 0; j < MAXNHNR; j++) {
  507.                         Nhnr[j] = 0;
  508.                     }
  509.                     i = 0;
  510.                 } else if (i >= MAXNHNR) {
  511.                     (void) sprintf(buf, " over NH limit (%d)",
  512.                     MAXNHNR);
  513.                     Error(WARN, LINE, buf, NULL);
  514.                 }
  515.             } else
  516.                 i = 0;
  517.             Nhnr[i]++;
  518.             for (j = i + 1; j < MAXNHNR; j++) {
  519.                 Nhnr[j] = 0;
  520.             }
  521.             s1 = buf;
  522.             for (j = 0; j <= i; j++) {
  523.                 (void) sprintf(s1, "%d.", Nhnr[j]);
  524.                 s1 += strlen(buf);
  525.             }
  526.             (void) Findstr("Nh", buf, 1);
  527.             return;
  528.         }
  529.     }
  530.     /*
  531.      * Remaining lines should begin with a '.' unless an "after next"
  532.      * trap has failed.
  533.      */
  534.     if (line[0] != '.') {
  535.         if (Aftnxt != NULL)
  536.             Error(WARN, LINE, " failed .it: ", Aftnxt);
  537.         else
  538.             Error(WARN, LINE, " unrecognized line ", NULL);
  539.         return;
  540.     }
  541.     /*
  542.      * Evaluate expressions for "^\.(ta|ll|ls|in|ti|po|ne|sp|pl|nr)"
  543.      * Then process the commands.
  544.      */
  545.     if (regexec(Pat[11].pat, &line[1])) {
  546.         /*
  547.          * Establish default scale factor.
  548.          */
  549.         if ((line[1] == 'n' && line[2] == 'e')
  550.         ||  (line[1] == 's' && line[2] == 'p')
  551.         ||  (line[1] == 'p' && line[2] == 'l'))
  552.             exscale = Scalev;
  553.         else if (line[1] == 'n' && line[2] == 'r')
  554.             exscale = Scaleu;
  555.         else
  556.             exscale = Scalen;
  557.         /*
  558.          * Determine starting argument.
  559.          */
  560.         if (line[1] == 'n' && line[2] == 'r')
  561.             s1 = Field(3, line, 0);
  562.         else
  563.             s1 = Field(2, line, 0);
  564.         /*
  565.          * Evaluate expressions.
  566.          */
  567.         for (nexpr = 0; s1 != NULL &&*s1 != '\0'; ) {
  568.             while (*s1 == ' ' || *s1 == '\t')
  569.                 s1++;
  570.             if (*s1 == '+' || *s1 == '-')
  571.                 ssign = *s1++;
  572.             else
  573.                 ssign = '\0';
  574.             /*
  575.              * Process terms.
  576.              */
  577.             val = 0.0;
  578.             sp = -1;
  579.             c = '+';
  580.             s1--;
  581.             while (c == '+' || c == '*' || c == '%'
  582.             ||  c == ')' || c == '-' || c == '/') {
  583.                 op = c;
  584.                 s1++;
  585.                 tscale = exscale;
  586.                 tval = 0.0;
  587.             /*
  588.              * Pop stack on right parenthesis.
  589.              */
  590.                 if (op == ')') {
  591.                 tval = val;
  592.                 if (sp >= 0) {
  593.                     val = valstack[sp];
  594.                     op = opstack[sp];
  595.                     sp--;
  596.                 } else {
  597.                     Error(WARN, LINE,
  598.                     " expression stack underflow", NULL);
  599.                     return;
  600.                 }
  601.                 tscale = Scaleu;
  602.             /*
  603.              * Push stack on left parenthesis.
  604.              */
  605.                 } else if (*s1 == '(') {
  606.                 sp++;
  607.                 if (sp >= MAXSP) {
  608.                     Error(WARN, LINE,
  609.                        " expression stack overflow", NULL);
  610.                     return;
  611.                 }
  612.                 valstack[sp] = val;
  613.                 opstack[sp] = op;
  614.                 val = 0.0;
  615.                 c = '+';
  616.                 continue;
  617.                 } else if (*s1 == '\\') {
  618.                   s1++;
  619.                   switch(*s1) {
  620.             /*
  621.              * "\\"" begins a comment.
  622.              */
  623.                   case '"':
  624.                 while (*s1)
  625.                     s1++;
  626.                 break;;
  627.             /*
  628.              * Crude width calculation for "\\w"
  629.              */
  630.                   case 'w':
  631.                 s2 = ++s1;
  632.                 if (*s1) {
  633.                     s1++;
  634.                     while (*s1 && *s1 != *s2)
  635.                     s1++;
  636.                     tval = (double) (s1 - s2 - 1) * Scalen;
  637.                     if (*s1)
  638.                     s1++;
  639.                 }
  640.                 break;
  641.             /*
  642.              * Interpolate number register if "\\n".
  643.              */
  644.                   case 'n':
  645.                 i = (s1[1] == '(') ? 2: 1;
  646.                 s1 += i;
  647.                     if (*s1 && *s1 != ' ' && *s1 != '\t')
  648.                     nm[0] = *s1++;
  649.                 else
  650.                     nm[0] = '\0';
  651.                 if (i == 2 && *s1 && *s1 != ' ' && *s1 != '\t')
  652.                     nm[1] = *s1++;
  653.                 else
  654.                     nm[1] = '\0';
  655.                 if ((i = Findnum(nm, 0, 0)) >= 0)
  656.                     tval = Numb[i].val;
  657.                  }
  658.             /*
  659.              * Assemble numeric value.
  660.              */
  661.                 } else if (*s1 == '.' || isdigit(*s1)) {
  662.                 for (i = 0; isdigit(*s1) || *s1 == '.'; s1++) {
  663.                     if (*s1 == '.') {
  664.                     i = 10;
  665.                     continue;
  666.                     }
  667.                     d = (double) (*s1 - '0');
  668.                     if (i) {
  669.                     tval = tval + (d / (double) i);
  670.                     i = i * 10;
  671.                     } else
  672.                     tval = (tval * 10.0) + d;
  673.                 }
  674.                 } else {
  675.             /*
  676.              * It's not an expression.  Ignore extra scale.
  677.              */
  678.                 if ((i = Findscale(*s1, 0.0, 0)) < 0) {
  679.                     (void) sprintf(buf,
  680.                     " \"%s\" isn't an expression", s1);
  681.                     Error(WARN, LINE, buf, NULL);
  682.                 }
  683.                 s1++;
  684.                 }
  685.             /*
  686.              * Add term to expression value.
  687.              */
  688.                 if ((i = Findscale(*s1, 0.0, 0)) >= 0) {
  689.                 tval *= Scale[i].val;
  690.                 s1++;
  691.                 } else
  692.                 tval *= tscale;
  693.                 switch (op) {
  694.                 case '+':
  695.                 val += tval;
  696.                 break;
  697.                 case '-':
  698.                 val -= tval;
  699.                 break;
  700.                 case '*':
  701.                 val *= tval;
  702.                 break;
  703.                 case '/':
  704.                 case '%':
  705.                 i = (int) val;
  706.                 j = (int) tval;
  707.                 if (j == 0) {
  708.                     Error(WARN, LINE,
  709.                     (*s1 == '/') ? "div" : "mod",
  710.                         " by 0");
  711.                     return;
  712.                 }
  713.                 if (op == '/')
  714.                     val = (double) (i / j);
  715.                 else
  716.                     val = (double) (i % j);
  717.                 break;
  718.                 }
  719.                 c = *s1;
  720.             }
  721.             /*
  722.              * Save expression value and sign.
  723.              */
  724.             if (nexpr >= MAXEXP) {
  725.                 (void) sprintf(buf,
  726.                     " at expression limit of %d", MAXEXP);
  727.                 Error(WARN, LINE, buf, NULL);
  728.                 return;
  729.             }
  730.             exsign[nexpr] = ssign;
  731.             expr[nexpr] = val;
  732.             if (ssign == '-')
  733.                 sexpr[nexpr] = -1.0 * val;
  734.             else
  735.                 sexpr[nexpr] = val;
  736.             nexpr++;
  737.             while (*s1 == ' ' || *s1 == '\t')
  738.                 s1++;
  739.         }
  740.         /*
  741.          * Set parameters "(ll|ls|in|ti|po|pl)"
  742.          */
  743.         if (regexec(Pat[12].pat, &line[1])) {
  744.             nm[0] = line[1];
  745.             nm[1] = line[2];
  746.             if ((i = Findparms(nm)) < 0) {
  747.                 Error(WARN, LINE,
  748.                     " can't find parameter register ", nm);
  749.                 return;
  750.             }
  751.             if (nexpr == 0 || exscale == 0.0)
  752.                 j = Parms[i].prev;
  753.             else if (exsign[0] == '\0'
  754.                  ||  (nm[0] == 't' && nm[1] == 'i'))
  755.                  j = (int)(expr[0] / exscale);
  756.             else
  757.                 j = Parms[i].val + (int)(sexpr[0] / exscale);
  758.             Parms[i].prev = Parms[i].val;
  759.             Parms[i].val = j;
  760.             Pass3(DOBREAK, Parms[i].cmd, NULL, j);
  761.             return;
  762.         }
  763.         if (line[1] == 'n') {
  764.             switch(line[2]) {
  765.         /*
  766.          * Need - "^\.ne <expression>"
  767.          */
  768.             case 'e':
  769.                 if (nexpr && Scalev > 0.0)
  770.                     i = (int) ((expr[0]/Scalev) + 0.99);
  771.                 else
  772.                     i = 0;
  773.                 Pass3(DOBREAK, "need", NULL, i);
  774.                 return;
  775.         /*
  776.          * Number - "^\.nr <name> <expression>"
  777.          */
  778.             case 'r':
  779.                 if ((s1 = Field(2, line, 0)) == NULL) {
  780.                     Error(WARN, LINE, " bad number register",
  781.                         NULL);
  782.                     return;
  783.                 }
  784.                 if ((i = Findnum(s1, 0, 0)) < 0)
  785.                     i = Findnum(s1, 0, 1);
  786.                 if (nexpr < 1) {
  787.                     Numb[i].val = 0;
  788.                     return;
  789.                 }
  790.                 if (exsign[0] == '\0')
  791.                     Numb[i].val = (int) expr[0];
  792.                 else
  793.                     Numb[i].val += (int) sexpr[0];
  794.                 return;
  795.             }
  796.         }
  797.         /*
  798.          * Space - "^\.sp <expression>"
  799.          */
  800.         if (line[1] == 's' && line[2] == 'p') {
  801.             if (nexpr == 0)
  802.                 i = 1;
  803.             else
  804.                 i = (int)((expr[0] / Scalev) + 0.99);
  805.             while (i--)
  806.                 Pass3(DOBREAK, "space", NULL, 0);
  807.             return;
  808.         }
  809.         /*
  810.          * Tab positions - "^\.ta <pos1> <pos2> . . ."
  811.          */
  812.              if (line[1] == 't' && line[2] == 'a') {
  813.             tval = 0.0;
  814.             for (j = 0; j < nexpr; j++) {
  815.                 if (exsign[j] == '\0')
  816.                     tval = expr[j];
  817.                 else
  818.                     tval += sexpr[j];
  819.                 Tabs[j] = (int) (tval / Scalen);
  820.             }
  821.             Ntabs = nexpr;
  822.             return;
  823.         }
  824.     }
  825.     /*
  826.      * Remaining lines begin with a '.'.
  827.      */
  828.  
  829.     /*
  830.      * Adjust - "^\.ad"
  831.      */
  832.     if (line[1] == 'a' && line[2] == 'd') {
  833.         Pass3(NOBREAK, "both", NULL, 0);
  834.         return;
  835.     }
  836.     if (line[1] == 'b') {
  837.         switch (line[2]) {
  838.     /*
  839.      * Break - "^\.br"
  840.      */
  841.         case 'r':
  842.             Pass3(DOBREAK, "flush", NULL, 0);
  843.             return;
  844.     /*
  845.      * Begin new page - "^\.bp"
  846.      */
  847.         case 'p':
  848.             Pass3(DOBREAK, "need", NULL, 999);
  849.             return;
  850.         }
  851.     }
  852.     /*
  853.      * Center - "^\.ce"
  854.      */
  855.     if (line[1] == 'c' && line[2] == 'e') {
  856.         if ((s2 = Field(2, line, 0)) != NULL)
  857.             Centering = atoi(s2);
  858.         else
  859.             Centering = 1;
  860.         return;
  861.     }
  862.     /*
  863.      * Define string - "^\.ds"
  864.      */
  865.     if (line[1] == 'd' && line[2] == 's') {
  866.  
  867. common_ds:
  868.  
  869.         if (Asmname(&line[3], nm) == 0) {
  870. no_name:
  871.             Error(WARN, LINE, " no name", NULL);
  872.             return;
  873.         }
  874.         s3 = Field(3, line, 0);
  875.         s4 = Findstr(nm, s3, 1);
  876.         if (Hdft) {
  877.             /*
  878.              * Look for names LH, LF, CH, CF, RH, RF.
  879.              */
  880.             if ((nm[0] == 'L' || nm[0] == 'C' || nm[0] == 'R')
  881.             &&  (nm[1] == 'F' || nm[1] == 'H')) {
  882.                 (void) sprintf(buf, "%s", nm);
  883.                 Pass3(NOBREAK, buf, s4, 0);
  884.             }
  885.         }
  886.         return;
  887.     }
  888.     if (line[1] == 'f') {
  889.     /*
  890.      * Fill - "^\.fi"
  891.      */
  892.         if (line[2] == 'i') {
  893.             Fill = 1;
  894.             Pass3(DOBREAK, "flush", NULL, 0);
  895.             return;
  896.         }
  897.     /*
  898.      * Font - "^\.ft <font_name>"
  899.      */
  900.         if (line[2] == 't') {
  901.             if (line[3] == '\0' || line[4] == '\0')
  902.                 line[4] = 'P';
  903.             if (line[4] == 'P') {
  904.                 Font[0] = Prevfont;
  905.                 return;
  906.             }
  907.             for (i = 0; Fcode[i].nm; i++) {
  908.                 if (Fcode[i].nm == line[4])
  909.                     break;
  910.             }
  911.             if (Fcode[i].status == '\0') {
  912.                 Error(WARN, LINE, " bad font code", NULL);
  913.                 return;
  914.             }
  915.             Prevfont = Font[0];
  916.             Font[0] = line[4];
  917.             return;
  918.         }
  919.     }
  920.     /*
  921.      * Input trap - "^\.it [1 <command>]"
  922.      */
  923.     if (line[1] == 'i' && line[2] == 't') {
  924.         if ((s2 = Field(2, line, 0)) == NULL) {
  925.             Free(&Aftnxt);
  926.             return;
  927.         }
  928.         if ((i = atoi(s2)) != 1) {
  929.             Error(WARN, LINE, " first .it arg must be 1", NULL);
  930.             return;
  931.         }
  932.         if ((s3 = Field(3, line, 0)) == NULL)
  933.             Free(&Aftnxt);
  934.         else {
  935.             (void) sprintf(buf, "%s,%s",
  936.                 (Aftnxt == NULL) ? "" : Aftnxt, s3);
  937.             Free(&Aftnxt);
  938.             Aftnxt = Newstr(buf);
  939.         }
  940.         return;
  941.     }
  942.     /*
  943.      * "^\.i0", "^\.lg" and "^\.li" - do nothing
  944.      */
  945.     if ((line[1] == 'i' && line[2] == '0')
  946.     ||  (line[1] == 'l' && line[2] == 'g')
  947.     ||  (line[1] == 'l' && line[2] == 'i'))
  948.         return;
  949.     if (line[1] == 'n') {
  950.         switch (line[2]) {
  951.     /*
  952.      * No adjust "^\.na"
  953.      */
  954.         case 'a':
  955.             Pass3(NOBREAK, "left", NULL, 0);
  956.             return;
  957.     /*
  958.      * No fill - "^\.nf"
  959.      */
  960.         case 'f':
  961.             Fill = 0;
  962.             Pass3(DOBREAK, "flush", NULL, 0);
  963.             return;
  964.     /*
  965.      * No space - "^\.ns"
  966.      */
  967.         case 's':
  968.             Pass3(NOBREAK, "nospace", NULL, 0);
  969.             return;
  970.         }
  971.     }
  972.     /*
  973.      * Point size - "^\.ps"
  974.      */
  975.     if (line[1] == 'p' && line[2] == 's')
  976.         return;
  977.     if (line[1] == 'r') {
  978.         switch (line[2]) {
  979.     /*
  980.      * Remove macro or string - "^\.rm"
  981.      */
  982.         case 'm':
  983.             if (Asmname(&line[3], nm) == 0)
  984.                 goto no_name;
  985.             if ((i = Findmacro(nm, 0)) >= 0) {
  986.                 Delmacro(i);
  987.                 return;
  988.             }
  989.             (void) Findstr(nm, NULL, 0);
  990.             if (Sx < 0) {
  991.                 Error(WARN, LINE, " no macro/string", NULL);
  992.                 return;
  993.             }
  994.             Delstr(Sx);
  995.             return;
  996.     /*
  997.      * Remove register - "^\.rr"
  998.      */
  999.         case 'r':
  1000.             if (Asmname(&line[3], nm) == 0)
  1001.                 goto no_name;
  1002.             if ((i = Findnum(nm, 0, 0)) < 0) {
  1003.                 Error(WARN, LINE, " no register", NULL);
  1004.                 return;
  1005.             }
  1006.             Delnum(i);
  1007.             return;
  1008.     /*
  1009.      * Resume space - "^\.rs"
  1010.      */
  1011.         case 's':
  1012.             Pass3(NOBREAK, "yesspace", NULL, 0);
  1013.             return;
  1014.         }
  1015.     }
  1016.     /*
  1017.      * Message - "^\.tm"
  1018.      */
  1019.     if (line[1] == 't' && line[2] == 'm') {
  1020.         Pass3(MESSAGE, Inname,
  1021.             (line[3] == ' ') ? &line[4] : &line[3], NR);
  1022.         return;
  1023.     }
  1024.     /*
  1025.      * Vertical spacing - "^\.vs" (do nothing)
  1026.      */
  1027.     if (line[1] == 'v' && line[2] == 's')
  1028.         return;
  1029.     if (line[1] == '^') {
  1030.         switch(line[2]) {
  1031.     /*
  1032.      * Initialization - "^\.\^b (fh|HF|NH) [01]"
  1033.      *
  1034.      * fh = first page header status
  1035.      * HF = header/footer status
  1036.      * NH = initialize number headers
  1037.      */
  1038.         case 'b':
  1039.             if ((s1 = Field(2, line, 0)) == NULL)
  1040.                 return;
  1041.             if ((s2 = Field(3, line, 0)) == NULL)
  1042.                 i = 0;
  1043.             else
  1044.                 i = atoi(s2);
  1045.             if (s1[0] == 'f' && s1[1] == 'h')
  1046.                 Pass3(NOBREAK, "fph", NULL, i);
  1047.             else if (s1[0] == 'H' && s1[1] == 'F')
  1048.                 Hdft = i;
  1049.             else if (s1[0] == 'N' && s1[1] == 'H') {
  1050.                 for (i = 0; i < MAXNHNR; i++)
  1051.                     Nhnr[i] = 0;
  1052.             } else
  1053.                 Error(WARN, LINE, " unknown initialization",
  1054.                     NULL);
  1055.             return;
  1056.     /*
  1057.      * Character definitions - "^\.\^c"
  1058.      */
  1059.         case 'c':
  1060.  
  1061.             s2 = Field(2, line, 0);
  1062.             i = atoi(Field(3, line, 0));
  1063.             s4 = Field(4, line, 0);
  1064.             if (i < 0 || i > MAXLINE/2 || *s2 == '\0') {
  1065.                 Error(WARN, LINE, " bad character definition",
  1066.                     NULL);
  1067.                 return;
  1068.             }
  1069.             if (s4 == NULL)
  1070.                 s4 = "";
  1071.             else if (*s4 == '"')
  1072.                 s4++;
  1073.             s1 = buf;
  1074.             while ((s5 = strchr(s4, '\\')) != NULL) {
  1075.                 while (s5 > s4)
  1076.                     *s1++ = *s4++;
  1077.                 s4 = ++s5;
  1078.                 if (*s5 == '\\')
  1079.                     *s1++ = '\\';
  1080.                 else if (*s5 == 'b')
  1081.                     *s1++ = '\b';
  1082.                 if (*s4)
  1083.                     s4++;
  1084.             }
  1085.             while (*s1++ = *s4++)
  1086.                 ;
  1087.             if (*s2 == 'h' && *(s2+1) == 'y')
  1088.                 (void) Findhy(buf, i, 1);
  1089.             else
  1090.                 (void) Findchar(s2, i, buf, 1);
  1091.             return;
  1092.     /*
  1093.      * Debug - "^\.\^d"
  1094.      */
  1095.         case 'd':
  1096.             return;
  1097.     /*
  1098.      * Finalization - "\.\^e"
  1099.      */
  1100.         case 'e':
  1101.             return;
  1102.     /*
  1103.      * Font is OK - "\.\^f <font_name_character>"
  1104.      */
  1105.         case 'f':
  1106.             if (line[3] != '\0' && line[4] != '\0') {
  1107.                 for (i = 0; Fcode[i].nm; i++) {
  1108.                     if (line[4] == Fcode[i].nm) {
  1109.                         Fcode[i].status = '1';
  1110.                         return;
  1111.                     }
  1112.                 }
  1113.             }
  1114.             Error(WARN, LINE, " unknown font", NULL);
  1115.             return;
  1116.     /*
  1117.      * Resolutions - "\.\^r cpi horizontal vertical"
  1118.      */
  1119.         case 'r':
  1120.             if ((i = atoi(Field(3, line, 0))) <= 0
  1121.             ||  (j = atoi(Field(4, line, 0))) <= 0) {
  1122.                 Error(WARN, LINE, " bad cpi resolutions",
  1123.                     NULL);
  1124.                 return;
  1125.             }
  1126.             tval = (double) (240.0 / (double) i);
  1127.             if (Findscale('m', tval, 1) < 0)
  1128.                 Error(FATAL, LINE, " missing Scal['m']",
  1129.                     NULL);
  1130.             Scalen = tval;
  1131.             if (Scalen <= 0.0) {
  1132.                 (void) sprintf(buf, " bad Scale['n'] (%f)",
  1133.                     Scalen);
  1134.                 Error(FATAL, LINE, buf, NULL);
  1135.             }
  1136.             if (Findscale('n', tval, 1) < 0)
  1137.                 Error(FATAL, LINE, " missing Scale['n']",
  1138.                     NULL);
  1139.             Scalev = (double) (240.0 / (double) j);
  1140.             if (Scalev <= 0.0) {
  1141.                 (void) sprintf(buf, " bad Scale['v'] (%f)",
  1142.                     Scalen);
  1143.                 Error(FATAL, LINE, buf, NULL);
  1144.             }
  1145.             if (Findscale('v', Scalev, 1) < 0)
  1146.                 Error(FATAL, LINE, " missing Scale['v']",
  1147.                     NULL);
  1148.             return;
  1149.     /*
  1150.      * Error file - "^\.\^x <name>"
  1151.      */
  1152.         case 'x':
  1153.             return;
  1154.     /*
  1155.      * Set line number and file name - "^\.\^# <number> <file>"
  1156.      *
  1157.      * Lock line number and file name - "^\.\^= <number> <file>"
  1158.      */
  1159.         case '#':
  1160.         case '=':
  1161.             if ((s1 = Field(2, line, 0)) != NULL)
  1162.                 P2il = atoi(s1) - 1;
  1163.             else
  1164.                 P2il = 0;
  1165.             Lockil = (line[2] == '#') ? 0 : 1;
  1166.             Free(&P2name);
  1167.             if (Field(3, line, 1) != NULL) {
  1168.                 P2name = F;
  1169.                 F = NULL;
  1170.             } else
  1171.                 P2name = NULL;
  1172.             return;
  1173.         }
  1174.     }
  1175.     /*
  1176.      * Comment - "^\.\\"
  1177.      */
  1178.     if (line[1] == '\\')
  1179.         return;
  1180.     /*
  1181.      * Unknown command starting with a '.'.
  1182.      */
  1183.     Error(WARN, LINE, " unknown command", NULL);
  1184.     return;
  1185. }
  1186. 
  1187.