home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 5 Edit / 05-Edit.zip / UE311C.ZIP / SEARCH.C < prev    next >
C/C++ Source or Header  |  1991-10-29  |  43KB  |  1,788 lines

  1. /*
  2.  * The functions in this file implement commands that search in the forward
  3.  * and backward directions.
  4.  *
  5.  * (History comments formerly here have been moved to history.c)
  6.  *
  7.  */
  8.  
  9. #include <stdio.h>
  10. #include "estruct.h"
  11. #include "eproto.h"
  12. #include "edef.h"
  13. #include "elang.h"
  14.  
  15. /* The variables matchline and matchoff hold the line
  16.  * and offset position of the *start* of the match.
  17.  */
  18. static int    matchlen;
  19. static int    matchoff;
  20. static LINE    *matchline;
  21. static int    patlenadd;
  22. static int    lastchfjump, lastchbjump;
  23. static int    deltaf[HICHAR], deltab[HICHAR];
  24. static int    replen;        /* length of replacement string */
  25.  
  26. #if    MAGIC
  27. static int        group_count;
  28. static int    group_len[MAXGROUPS];
  29. static REGION    group_reg[MAXGROUPS];
  30. #endif
  31.  
  32. /*
  33.  * forwsearch -- Search forward.  Get a search string from the user, and
  34.  *    search for the string.  If found, reset the "." to be just after
  35.  *    the match string, and (perhaps) repaint the display.
  36.  */
  37. int PASCAL NEAR forwsearch(f, n)
  38. int f, n;    /* default flag / numeric argument */
  39. {
  40.     register int    status;
  41.  
  42.     /* If n is negative, search backwards.
  43.      * Otherwise proceed by asking for the search string.
  44.      */
  45.     if (n < 0)
  46.         return (backsearch(f, -n));
  47.  
  48.     /* Ask the user for the text of a pattern.  If the response is
  49.      * TRUE (responses other than FALSE are possible), search for
  50.      * pattern for up to n times, as long as the pattern is there
  51.      * to be found.
  52.      */
  53.     if ((status = readpattern(TEXT78, &pat[0], TRUE)) == TRUE)
  54.         status = forwhunt(f, n);
  55. /*                "Search" */
  56.  
  57.     return (status);
  58. }
  59.  
  60. /*
  61.  * forwhunt -- Search forward for a previously acquired search string.
  62.  *    If found, reset the "." to be just after the match string,
  63.  *    and (perhaps) repaint the display.
  64.  */
  65. int PASCAL NEAR forwhunt(f, n)
  66. int f, n;    /* default flag / numeric argument */
  67. {
  68.     register int    spoint = PTEND;
  69.     register int    status;
  70.  
  71.     if (n < 0)        /* search backwards */
  72.         return (backhunt(f, -n));
  73.  
  74.     /* Make sure a pattern exists, or that we didn't switch
  75.      * into MAGIC mode after we entered the pattern.
  76.      */
  77.     if (pat[0] == '\0') {
  78.         mlwrite(TEXT80);
  79. /*            "No pattern set" */
  80.         return FALSE;
  81.     }
  82.  
  83. #if    MAGIC
  84.     if ((curwp->w_bufp->b_mode & MDMAGIC) && mcpat[0].mc_type == MCNIL) {
  85.         if (!mcstr())
  86.             return FALSE;
  87.     }
  88. #endif
  89.  
  90.     /*
  91.      * Do one extra search to get us past our current
  92.      * match, if the search type has us at the start
  93.      * of a match, instead of after a match.
  94.      */
  95.     if (searchtype == SRBEGIN) {
  96.         spoint = PTBEG;
  97.         if (lastflag & CFSRCH)
  98.             n = (n > 2)? (n + 1): 2;
  99.     }
  100.  
  101. #if    MAGIC
  102.     if (magical && (curwp->w_bufp->b_mode & MDMAGIC))
  103.         status = mcscanner(FORWARD, spoint, n);
  104.     else
  105. #endif
  106.         status = scanner(FORWARD, spoint, n);
  107.  
  108.     /* Complain if not there.
  109.      */
  110.     if (status == FALSE)
  111.         mlwrite(TEXT79);
  112. /*            "Not found" */
  113.  
  114.     thisflag |= CFSRCH;
  115.     return (status);
  116. }
  117.  
  118. /*
  119.  * backsearch -- Reverse search.  Get a search string from the user, and
  120.  *    search, starting at "." and proceeding toward the front of the buffer.
  121.  *    If found "." is left pointing at the first character of the pattern
  122.  *    (the last character that was matched).
  123.  */
  124. int PASCAL NEAR backsearch(f, n)
  125. int f, n;    /* default flag / numeric argument */
  126. {
  127.     register int    status;
  128.  
  129.     /* If n is negative, search forwards.
  130.      * Otherwise proceed by asking for the search string.
  131.      */
  132.     if (n < 0)
  133.         return (forwsearch(f, -n));
  134.  
  135.     /* Ask the user for the text of a pattern.  If the response is
  136.      * TRUE (responses other than FALSE are possible), search for
  137.      * pattern for up to n times, as long as the pattern is there
  138.      * to be found.
  139.      */
  140.     if ((status = readpattern(TEXT81, &pat[0], TRUE)) == TRUE)
  141.         status = backhunt(f, n);
  142. /*                "Reverse search" */
  143.  
  144.     return (status);
  145. }
  146.  
  147. /*
  148.  * backhunt -- Reverse search for a previously acquired search string,
  149.  *    starting at "." and proceeding toward the front of the buffer.
  150.  *    If found "." is left pointing at the first character of the pattern
  151.  *    (the last character that was matched).
  152.  */
  153. int PASCAL NEAR backhunt(f, n)
  154. int f, n;    /* default flag / numeric argument */
  155. {
  156.     register int    spoint = PTBEG;
  157.     register int    status;
  158.  
  159.     if (n < 0)
  160.         return (forwhunt(f, -n));
  161.  
  162.     /* Make sure a pattern exists, or that we didn't switch
  163.      * into MAGIC mode after we entered the pattern.
  164.      */
  165.     if (tap[0] == '\0') {
  166.         mlwrite(TEXT80);
  167. /*            "No pattern set" */
  168.         return FALSE;
  169.     }
  170. #if    MAGIC
  171.     if ((curwp->w_bufp->b_mode & MDMAGIC) && tapcm[0].mc_type == MCNIL) {
  172.         if (!mcstr())
  173.             return FALSE;
  174.     }
  175. #endif
  176.  
  177.     /*
  178.      * Do one extra search to get us past our current
  179.      * match, if the search type has us at the start
  180.      * of a match, instead of after a match.
  181.      */
  182.     if (searchtype == SREND) {
  183.         spoint = PTEND;
  184.         if (lastflag & CFSRCH)
  185.             n = (n > 2)? (n + 1): 2;
  186.     }
  187.  
  188. #if    MAGIC
  189.     if (magical && (curwp->w_bufp->b_mode & MDMAGIC))
  190.         status = mcscanner(REVERSE, spoint, n);
  191.     else
  192. #endif
  193.         status = scanner(REVERSE, spoint, n);
  194.  
  195.     /* Complain if not there.
  196.      */
  197.     if (status == FALSE)
  198.         mlwrite(TEXT79);
  199. /*            "Not found" */
  200.  
  201.     thisflag |= CFSRCH;
  202.     return (status);
  203. }
  204.  
  205. #if    MAGIC
  206. /*
  207.  * mcscanner -- Search for a meta-pattern in either direction.  If found,
  208.  *    reset the "." to be at the start or just after the match string,
  209.  *    and (perhaps) repaint the display.
  210.  */
  211. int PASCAL NEAR mcscanner(direct, beg_or_end, repeats)
  212. int    direct;        /* which way to go.*/
  213. int    beg_or_end;    /* put point at beginning or end of pattern.*/
  214. int    repeats;    /* search repetitions.*/
  215. {
  216.     MC    *mcpatrn;    /* pointer into pattern */
  217.     LINE    *curline;    /* current line during scan */
  218.     int    curoff;        /* position within current line */
  219.  
  220.     /* If we are going in reverse, then the 'end' is actually
  221.      * the beginning of the pattern.  Toggle it.
  222.      */
  223.     beg_or_end ^= direct;
  224.  
  225.     /* Another directional problem: if we are searching forwards,
  226.      * use the pattern in mcpat[].  Otherwise, use the reversed
  227.      * pattern in tapcm[].
  228.      */
  229.     mcpatrn = (direct == FORWARD)? &mcpat[0]: &tapcm[0];
  230.  
  231.     /* Setup local scan pointers to global ".".
  232.      */
  233.     curline = curwp->w_dotp;
  234.     curoff  = curwp->w_doto;
  235.  
  236.     /* Scan each character until we hit the head link record.
  237.      */
  238.     while (!boundry(curline, curoff, direct)) {
  239.         /* Save the current position in case we need to
  240.          * restore it on a match, and initialize matchlen to
  241.          * zero in case we are doing a search for replacement.
  242.          */
  243.         matchline = curline;
  244.         matchoff = curoff;
  245.         matchlen = 0;
  246.  
  247.         if (amatch(mcpatrn, direct, &curline, &curoff)) {
  248.             /* A SUCCESSFULL MATCH!!!
  249.              * Flag that we have moved,
  250.              * reset the global "." pointers.
  251.              */
  252.             curwp->w_flag |= WFMOVE;
  253.             if (beg_or_end == PTEND)    /* at end of string */
  254.             {
  255.                 curwp->w_dotp = curline;
  256.                 curwp->w_doto = curoff;
  257.             }
  258.             else        /* at beginning of string */
  259.             {
  260.                 curwp->w_dotp = matchline;
  261.                 curwp->w_doto = matchoff;
  262.             }
  263.  
  264.             /* If we're heading in reverse, set the "match"
  265.              * pointers to the start of the string, for savematch().
  266.              */
  267.             if (direct == REVERSE) {
  268.                 matchline = curline;
  269.                 matchoff = curoff;
  270.             }
  271.  
  272.             if (savematch() == ABORT)
  273.                 return ABORT;
  274.  
  275.             /* Continue scanning if we haven't found
  276.              * the nth match.
  277.              */
  278.             if (--repeats <= 0)
  279.                 return TRUE;
  280.         }
  281.  
  282.         /* Advance the cursor.
  283.          */
  284.         nextch(&curline, &curoff, direct);
  285.     }
  286.  
  287.     return FALSE;    /* We could not find a match.*/
  288. }
  289.  
  290. /*
  291.  * amatch -- Search for a meta-pattern in either direction.  Based on the
  292.  *    recursive routine amatch() (for "anchored match") in
  293.  *    Kernighan & Plauger's "Software Tools".
  294.  */
  295. int PASCAL NEAR    amatch(mcptr, direct, pcwline, pcwoff)
  296. register MC    *mcptr;        /* pattern to scan for */
  297. int        direct;        /* which way to go.*/
  298. LINE        **pcwline;    /* current line during scan */
  299. int        *pcwoff;    /* position within current line */
  300. {
  301.     register int    c;    /* character at current position */
  302.     LINE    *curline;    /* current line during scan */
  303.     int    curoff;        /* position within current line */
  304.     int    n_chars;    /* number of chars matched in a closure.*/
  305.     int    cl_min;        /* minimum number of chars matched in closure.*/
  306.     int    cl_type;    /* Which closure type?.*/
  307.  
  308.     /* Set up local scan pointers to ".".
  309.      */
  310.     curline = *pcwline;
  311.     curoff = *pcwoff;
  312.  
  313.     /*
  314.      * Loop through the meta-pattern, laughing all the way.
  315.      */
  316.     while (mcptr->mc_type != MCNIL) {
  317.         /* Is the current meta-character modified
  318.          * by a closure?
  319.          */
  320.         if (cl_type = (mcptr->mc_type & ALLCLOS)) {
  321.             c = nextch(&curline, &curoff, direct);
  322.             n_chars = 0;
  323.  
  324.             if (cl_type == ZEROONE) {
  325.                 cl_min = 0;
  326.  
  327.                 if (mceq(c, mcptr)) {
  328.                     c = nextch(&curline, &curoff, direct);
  329.                     n_chars++;
  330.                 }
  331.             }
  332.             else
  333.             {
  334.                 /* Minimum number of characters that may
  335.                  * match is 0 or 1.
  336.                  */
  337.                 cl_min = (cl_type == CLOSURE_1);
  338.  
  339.                 /* Match as many characters as possible
  340.                  * against the current meta-character.
  341.                  */
  342.                 while (mceq(c, mcptr)) {
  343.                     c = nextch(&curline, &curoff, direct);
  344.                     n_chars++;
  345.                 }
  346.             }
  347.  
  348.             /* We are now at the character that made us
  349.              * fail.  Try to match the rest of the pattern.
  350.              * Shrink the closure by one for each failure.
  351.              */
  352.             mcptr++;
  353.             matchlen += n_chars;
  354.             for (;;) {
  355.                 if (n_chars < cl_min) {
  356.                     matchlen -= n_chars;
  357.                     return FALSE;
  358.                 }
  359.  
  360.                 nextch(&curline, &curoff, direct ^ REVERSE);
  361.  
  362.                 if (amatch(mcptr, direct, &curline, &curoff))
  363.                     goto success;
  364.                 matchlen--;
  365.                 n_chars--;
  366.             }
  367.         }
  368.         else if (mcptr->mc_type == GRPBEG) {
  369.             if (direct == FORWARD) {
  370.                 group_reg[mcptr->u.group_no].r_offset = curoff;
  371.                 group_reg[mcptr->u.group_no].r_linep = curline;
  372.             }
  373.             group_reg[mcptr->u.group_no].r_size = -matchlen;
  374.         }
  375.         else if (mcptr->mc_type == GRPEND) {
  376.             if (direct == REVERSE) {
  377.                 group_reg[mcptr->u.group_no].r_offset = curoff;
  378.                 group_reg[mcptr->u.group_no].r_linep = curline;
  379.             }
  380.             group_len[mcptr->u.group_no] = matchlen;
  381.         }
  382.         else if (mcptr->mc_type == BOL) {
  383.             if (curoff != 0)
  384.                 return FALSE;
  385.         }
  386.         else if (mcptr->mc_type == EOL) {
  387.             if (curoff != llength(curline))
  388.                 return FALSE;
  389.         }
  390.         else
  391.         {
  392.             /* A character to compare against
  393.              * the meta-character equal function.
  394.              * If we still match, increment the length.
  395.              */
  396.             if (!mceq(nextch(&curline, &curoff, direct), mcptr))
  397.                 return FALSE;
  398.  
  399.             matchlen++;
  400.         }
  401.  
  402.         mcptr++;
  403.     }            /* End of mcptr loop.*/
  404.  
  405.     /* A SUCCESSFULL MATCH!!!
  406.      * Reset the "." pointers.
  407.      */
  408. success:
  409.     *pcwline = curline;
  410.     *pcwoff  = curoff;
  411.  
  412.     return (TRUE);
  413. }
  414. #endif
  415.  
  416. /*
  417.  * scanner -- Search for a pattern in either direction.  If found,
  418.  *    reset the "." to be at the start or just after the match string,
  419.  *    and (perhaps) repaint the display.
  420.  *    Fast version using simplified version of Boyer and Moore
  421.  *    Software-Practice and Experience, vol 10, 501-506 (1980)
  422.  */
  423. int PASCAL NEAR scanner(direct, beg_or_end, repeats)
  424. int    direct;        /* which way to go.*/
  425. int    beg_or_end;    /* put point at beginning or end of pattern.*/
  426. int    repeats;    /* search repetitions.*/
  427. {
  428.     register int    c;        /* character at current position */
  429.     register char    *patptr;    /* pointer into pattern */
  430.     char    *patrn;            /* string to scan for */
  431.     LINE    *curline;        /* current line during scan */
  432.     int    curoff;            /* position within current line */
  433.     LINE    *scanline;        /* current line during scanning */
  434.     int    scanoff;        /* position in scanned line */
  435.     int    jump;            /* next offset */
  436.  
  437.     /* If we are going in reverse, then the 'end' is actually
  438.      * the beginning of the pattern.  Toggle it.
  439.      */
  440.     beg_or_end ^= direct;
  441.  
  442.     /* Another directional problem: if we are searching
  443.      * forwards, use the pattern in pat[], and the jump
  444.      * size in lastchfjump.  Otherwise, use the reversed
  445.      * pattern in tap[], and the jump size of lastchbjump.
  446.      */
  447.     if (direct == FORWARD) {
  448.         patrn = &pat[0];
  449.         jump = lastchfjump;
  450.     }
  451.     else
  452.     {
  453.         patrn = &tap[0];
  454.         jump = lastchbjump;
  455.     }
  456.  
  457.     /* Set up local pointers to global ".".
  458.      */
  459.     curline = curwp->w_dotp;
  460.     curoff = curwp->w_doto;
  461.  
  462.     /* Scan each character until we hit the head link record.
  463.      * Get the character resolving newlines, offset
  464.      * by the pattern length, i.e. the last character of the
  465.      * potential match.
  466.      */
  467.     if (!fbound(patlenadd, &curline, &curoff, direct)) {
  468.         do
  469.         {
  470.             /* Set up the scanning pointers, and save
  471.              * the current position in case we match
  472.              * the search string at this point.
  473.              */
  474.             scanline = matchline = curline;
  475.             scanoff  = matchoff  = curoff;
  476.  
  477.             patptr = patrn;
  478.  
  479.             /* Scan through the pattern for a match.
  480.              */
  481.             while ((c = *patptr++) != '\0')
  482.                 if (!eq(c, nextch(&scanline, &scanoff, direct)))
  483.                     goto fail;
  484.  
  485.             /* A SUCCESSFULL MATCH!!!
  486.              * Flag that we have moved and reset the
  487.              * global "." pointers.
  488.              */
  489.             curwp->w_flag |= WFMOVE;
  490.             if (beg_or_end == PTEND)    /* at end of string */
  491.             {
  492.                 curwp->w_dotp = scanline;
  493.                 curwp->w_doto = scanoff;
  494.             }
  495.             else        /* at beginning of string */
  496.             {
  497.                 curwp->w_dotp = matchline;
  498.                 curwp->w_doto = matchoff;
  499.             }
  500.  
  501.             /* If we're heading in reverse, set the "match"
  502.              * pointers to the start of the string, for savematch().
  503.              */
  504.             if (direct == REVERSE) {
  505.                 matchline = scanline;
  506.                 matchoff = scanoff;
  507.             }
  508.  
  509.             if (savematch() == ABORT)
  510.                 return ABORT;
  511.  
  512.             /* Continue scanning if we haven't found
  513.              * the nth match.
  514.              */
  515.             if (--repeats <= 0)
  516.                 return (TRUE);
  517.             else
  518.             {
  519.                 curline = scanline;
  520.                 curoff = scanoff;
  521.             }
  522.  
  523. fail:;            /* continue to search */
  524.         } while (!fbound(jump, &curline, &curoff, direct));
  525.     }
  526.  
  527.     return FALSE;    /* We could not find a match */
  528. }
  529.  
  530. /*
  531.  * fbound -- Return information depending on whether we have hit a boundry
  532.  *    (and may therefore search no further) or if a trailing character
  533.  *    of the search string has been found.  See boundry() for search
  534.  *    restrictions.
  535.  */
  536. int PASCAL NEAR    fbound(jump, pcurline, pcuroff, dir)
  537. LINE    **pcurline;
  538. int    *pcuroff, dir, jump;
  539. {
  540.     register int    spare, curoff;
  541.     register LINE    *curline;
  542.  
  543.     curline = *pcurline;
  544.     curoff = *pcuroff;
  545.  
  546.     if (dir == FORWARD) {
  547.         while (jump != 0) {
  548.             curoff += jump;
  549.             spare = curoff - llength(curline);
  550.  
  551.             if (curline == curbp->b_linep)
  552.                 return (TRUE);    /* hit end of buffer */
  553.  
  554.             while (spare > 0) {
  555.                 curline = lforw(curline);/* skip to next line */
  556.                 curoff = spare - 1;
  557.                 spare = curoff - llength(curline);
  558.                 if (curline == curbp->b_linep)
  559.                     return (TRUE);    /* hit end of buffer */
  560.             }
  561.  
  562.             if (spare == 0)
  563.                 jump = deltaf[(int) '\r'];
  564.             else
  565.                 jump = deltaf[(int) lgetc(curline, curoff)];
  566.         }
  567.  
  568.         /* The last character matches, so back up to start
  569.          * of possible match.
  570.          */
  571.         curoff -= patlenadd;
  572.  
  573.         while (curoff < 0) {
  574.             curline = lback(curline);    /* skip back a line */
  575.             curoff += llength(curline) + 1;
  576.         }
  577.  
  578.     }
  579.     else            /* Reverse.*/
  580.     {
  581.         jump++;        /* allow for offset in reverse */
  582.         while (jump != 0) {
  583.             curoff -= jump;
  584.             while (curoff < 0) {
  585.                 curline = lback(curline);    /* skip back a line */
  586.                 curoff += llength(curline) + 1;
  587.                 if (curline == curbp->b_linep)
  588.                     return (TRUE);    /* hit end of buffer */
  589.             }
  590.  
  591.             if (curoff == llength(curline))
  592.                 jump = deltab[(int) '\r'];
  593.             else
  594.                 jump = deltab[(int) lgetc(curline, curoff)];
  595.         }
  596.  
  597.         /* The last character matches, so back up to start
  598.          * of possible match.
  599.          */
  600.         curoff += patlenadd + 1;
  601.         spare = curoff - llength(curline);
  602.         while (spare > 0) {
  603.             curline = lforw(curline);    /* skip back a line */
  604.             curoff = spare - 1;
  605.             spare = curoff - llength(curline);
  606.         }
  607.     }
  608.  
  609.     *pcurline = curline;
  610.     *pcuroff = curoff;
  611.     return FALSE;
  612. }
  613.  
  614. /*
  615.  * setjtable -- Settting up search delta forward and delta backward
  616.  *    tables.  The reverse search string and string lengths are
  617.  *    set here, for table initialization and for substitution
  618.  *    purposes.  The default for any character to jump is the
  619.  *    pattern length.
  620.  */
  621. VOID PASCAL NEAR setjtable()
  622. {
  623.     int    i;
  624.  
  625.     rvstrcpy(tap, pat);
  626.     patlenadd = (matchlen = strlen(pat)) - 1;
  627.  
  628.     for (i = 0; i < HICHAR; i++) {
  629.         deltaf[i] = matchlen;
  630.         deltab[i] = matchlen;
  631.     }
  632.  
  633.     /* Now put in the characters contained
  634.      * in the pattern, duplicating the CASE.
  635.      */
  636.     for (i = 0; i < patlenadd; i++) {
  637.         if (isletter(pat[i]))
  638.             deltaf[(unsigned int) chcase((unsigned int) pat[i])]
  639.              = patlenadd - i;
  640.         deltaf[(unsigned int) pat[i]] = patlenadd - i;
  641.  
  642.         if (isletter(tap[i]))
  643.             deltab[(unsigned int) chcase((unsigned int) tap[i])]
  644.              = patlenadd - i;
  645.         deltab[(unsigned int) tap[i]] = patlenadd - i;
  646.     }
  647.  
  648.     /* The last character will have the pattern length
  649.      * unless there are duplicates of it.  Get the number to
  650.      * jump from the arrays delta, and overwrite with zeroes
  651.      * in delta duplicating the CASE.
  652.      *
  653.      * The last character is, of course, the first character
  654.      * of the reversed string.
  655.      */
  656.     lastchfjump = patlenadd + deltaf[(unsigned int) tap[0]];
  657.     lastchbjump = patlenadd + deltab[(unsigned int) pat[0]];
  658.  
  659.     if (isletter(tap[0]))
  660.         deltaf[(unsigned int) chcase(tap[0])] = 0;
  661.     deltaf[(unsigned int) tap[0]] = 0;
  662.  
  663.     if (isletter(pat[0]))
  664.         deltab[(unsigned int) chcase(pat[0])] = 0;
  665.     deltab[(unsigned int) pat[0]] = 0;
  666. }
  667.  
  668. /*
  669.  * rvstrcpy -- Reverse string copy.
  670.  */
  671. VOID PASCAL NEAR rvstrcpy(rvstr, str)
  672. register char    *rvstr, *str;
  673. {
  674.     register int i;
  675.  
  676.     str += (i = strlen(str));
  677.  
  678.     while (i-- > 0)
  679.         *rvstr++ = *--str;
  680.  
  681.     *rvstr = '\0';
  682. }
  683.  
  684. /*
  685.  * eq -- Compare two characters.  The "bc" comes from the buffer, "pc"
  686.  *    from the pattern.  If we are not in EXACT mode, fold out the case.
  687.  */
  688. int PASCAL NEAR eq(bc, pc)
  689. register int    bc;
  690. register int    pc;
  691. {
  692.     if ((curwp->w_bufp->b_mode & MDEXACT) == 0) {
  693.         if (islower(bc))
  694.             bc = chcase(bc);
  695.  
  696.         if (islower(pc))
  697.             pc = chcase(pc);
  698.     }
  699.  
  700.     return (bc == pc);
  701. }
  702.  
  703. /*
  704.  * readpattern -- Read a pattern.  Stash it in apat.  If it is the
  705.  *    search string (which means that the global variable pat[]
  706.  *    has been passed in), create the reverse pattern and the magic
  707.  *    pattern, assuming we are in MAGIC mode (and #defined that way).
  708.  *
  709.  *    Apat is not updated if the user types in an empty line.  If
  710.  *    the user typed an empty line, and there is no old pattern, it is
  711.  *    an error.  Display the old pattern, in the style of Jeff Lomicka.
  712.  *    There is some do-it-yourself control expansion.  Change to using
  713.  *    <META> to delimit the end-of-pattern to allow <NL>s in the search
  714.  *    string. 
  715.  */
  716. int PASCAL NEAR readpattern(prompt, apat, srch)
  717. char    *prompt;
  718. char    apat[];
  719. int    srch;
  720. {
  721.     register int    status;
  722.     char        tpat[NPAT+20];
  723.  
  724.     strcpy(tpat, prompt);    /* copy prompt to output string */
  725.     strcat(tpat, " [");    /* build new prompt string */
  726.     expandp(&apat[0], &tpat[strlen(tpat)], NPAT/2);    /* add old pattern */
  727.     strcat(tpat, "]");
  728.  
  729.     /* display the proper current search terminator character */
  730.     switch (sterm) {
  731.  
  732.         case CTRL | '[':    strcat(tpat, "<META>"); break;
  733.         case CTRL | 'M':    strcat(tpat, "<NL>"); break;
  734.         default:        strcat(tpat, "<");
  735.                     cmdstr(sterm, &(tpat[strlen(tpat)]));
  736.                     strcat(tpat, ">");
  737.     }
  738.     strcat(tpat, ": ");
  739.  
  740.     /* Read a pattern.  Either we get one,
  741.      * or we just get the META charater, and use the previous pattern.
  742.      * Then, if it's the search string, make a reversed pattern.
  743.      * *Then*, make the meta-pattern, if we are defined that way.
  744.      */
  745.     if ((status = mltreply(tpat, tpat, NPAT, sterm)) == TRUE) {
  746.         lastflag &= ~CFSRCH;
  747.         strcpy(apat, tpat);
  748.  
  749.         /* If we are doing the search string,
  750.          * set the delta tables.
  751.          */
  752.         if (srch)
  753.             setjtable();
  754.  
  755.     }
  756.     else if (status == FALSE && apat[0] != 0)    /* Old one */
  757.         status = TRUE;
  758.  
  759. #if    MAGIC
  760.     /* Only make the meta-pattern if in magic mode, since the
  761.      * pattern in question might have an invalid meta combination.
  762.      */
  763.     if (status == TRUE)
  764.         if ((curwp->w_bufp->b_mode & MDMAGIC) == 0) {
  765.             mcclear();
  766.             rmcclear();
  767.         }
  768.         else
  769.             status = srch? mcstr(): rmcstr();
  770. #endif
  771.     return (status);
  772. }
  773.  
  774. /*
  775.  * savematch -- We found the pattern?  Let's save it away.
  776.  */
  777. int PASCAL NEAR savematch()
  778. {
  779.     register int    j;
  780.     REGION        tmpreg;
  781.  
  782.     if (patmatch != NULL)
  783.         free(patmatch);
  784.  
  785.     if ((patmatch = malloc(matchlen + 1)) == NULL) {
  786.         mlwrite(TEXT94);
  787. /*            "%%Out of memory" */
  788.         return ABORT;
  789.     }
  790.  
  791.     tmpreg.r_offset = matchoff;
  792.     tmpreg.r_linep = matchline;
  793.     tmpreg.r_size = matchlen;
  794.     regtostr(patmatch, &tmpreg);
  795.  
  796. #if    MAGIC
  797.     /*
  798.      * Save the groups.
  799.      */
  800.     grpmatch[0] = patmatch;
  801.     for (j = 1; j <= group_count; j++) {
  802.         group_reg[j].r_size += group_len[j];
  803.         if (grpmatch[j] != NULL) {
  804.             free(grpmatch[j]);
  805.         }
  806.  
  807.         if ((grpmatch[j] = malloc(group_reg[j].r_size + 1)) != NULL) {
  808.             regtostr(grpmatch[j], &group_reg[j]);
  809.         } else {
  810.             mlwrite(TEXT94);
  811. /*            "%%Out of memory" */
  812.             return ABORT;
  813.         }
  814.     }
  815. #endif
  816.     return TRUE;
  817. }
  818.  
  819. /*
  820.  * sreplace -- Search and replace.
  821.  */
  822. int PASCAL NEAR sreplace(f, n)
  823. int f;        /* default flag */
  824. int n;        /* # of repetitions wanted */
  825. {
  826.     return (replaces(FALSE, f, n));
  827. }
  828.  
  829. /*
  830.  * qreplace -- search and replace with query.
  831.  */
  832. int PASCAL NEAR qreplace(f, n)
  833. int f;        /* default flag */
  834. int n;        /* # of repetitions wanted */
  835. {
  836.     return (replaces(TRUE, f, n));
  837. }
  838.  
  839. /*
  840.  * replaces -- Search for a string and replace it with another
  841.  *    string.  Query might be enabled (according to kind).
  842.  */
  843. int PASCAL NEAR    replaces(kind, f, n)
  844. int    kind;    /* Query enabled flag */
  845. int    f;    /* default flag */
  846. int    n;    /* # of repetitions wanted */
  847. {
  848.     register int status;    /* success flag on pattern inputs */
  849.     register int nummatch;    /* number of found matches */
  850.     int numsub;        /* number of substitutions */
  851.     int nlflag;        /* last char of search string a <NL>? */
  852.     int nlrepl;        /* was a replace done on the last line? */
  853.     char c;            /* input char for query */
  854.     char tpat[NPAT];    /* temporary to hold search pattern */
  855.     LINE *origline;        /* original "." position */
  856.     int origoff;        /* and offset (for . query option) */
  857.     LINE *lastline;        /* position of last replace and */
  858.     int lastoff;        /* offset (for 'u' query option) */
  859.     int oldmatchlen;    /* Closure may alter the match length.*/
  860.     static char *oldpatmatch = NULL;    /* allocated memory for un-do.*/
  861.  
  862.     if (curbp->b_mode & MDVIEW)    /* don't allow this command if    */
  863.         return (rdonly());    /* we are in read only mode    */
  864.  
  865.     /* Check for negative repetitions.
  866.      */
  867.     if (f && n < 0)
  868.         return FALSE;
  869.  
  870.     /* Ask the user for the text of a pattern.
  871.      */
  872.     if ((status = readpattern(
  873.         (kind == FALSE ? TEXT84 : TEXT85), &pat[0], TRUE)) != TRUE)
  874. /*                "Replace" */
  875. /*                    "Query replace" */
  876.         return (status);
  877.  
  878.     /* Ask for the replacement string, and get its length.
  879.      */
  880.     if ((status = readpattern(TEXT86, &rpat[0], FALSE)) == ABORT)
  881. /*                "with" */
  882.         return (status);
  883.  
  884.     /* Set up flags so we can make sure not to do a recursive
  885.      * replace on the last line.
  886.      */
  887.     nlflag = (pat[matchlen - 1] == '\r');
  888.     nlrepl = FALSE;
  889.  
  890.     if (kind) {
  891.         /* Build query replace question string.
  892.          */
  893.         strcpy(tpat, TEXT87);
  894. /*                "Replace '" */
  895.         expandp(&pat[0], &tpat[strlen(tpat)], NPAT/3);
  896.         strcat(tpat, TEXT88);
  897. /*                "' with '" */
  898.         expandp(&rpat[0], &tpat[strlen(tpat)], NPAT/3);
  899.         strcat(tpat, "'? ");
  900.  
  901.         /* Initialize last replaced pointers.
  902.          */
  903.         lastline = NULL;
  904.         lastoff = 0;
  905.     }
  906.  
  907.     /* Save original . position, reset the number of matches and
  908.      * substitutions, and scan through the file.
  909.      */
  910.     origline = curwp->w_dotp;
  911.     origoff = curwp->w_doto;
  912.     numsub = 0;
  913.     nummatch = 0;
  914.  
  915.     while ( (f == FALSE || n > nummatch) &&
  916.         (nlflag == FALSE || nlrepl == FALSE) ) {
  917.         /* Search for the pattern.
  918.          * If we search with a regular expression,
  919.          * matchlen is reset to the true length of
  920.          * the matched string.
  921.          */
  922. #if    MAGIC
  923.         if (magical && (curwp->w_bufp->b_mode & MDMAGIC)) {
  924.             if (!mcscanner(FORWARD, PTBEG, 1))
  925.                 break;
  926.         }
  927.         else
  928. #endif
  929.             if (!scanner(FORWARD, PTBEG, 1))
  930.                 break;        /* all done */
  931.  
  932.         ++nummatch;    /* Increment # of matches */
  933.  
  934.         /* Check if we are on the last line.
  935.          */
  936.         nlrepl = (lforw(curwp->w_dotp) == curwp->w_bufp->b_linep);
  937.  
  938.         /* Check for query.
  939.          */
  940.         if (kind) {
  941.             /* Get the query.
  942.              */
  943. pprompt:        mlwrite(&tpat[0]);
  944. qprompt:
  945.             update(TRUE);  /* show the proposed place to change */
  946.             c = tgetc();            /* and input */
  947.             mlerase();            /* and clear it */
  948.  
  949.             /* And respond appropriately.
  950.              */
  951.             switch (c) {
  952. #if    FRENCH
  953.                 case 'o':    /* oui, substitute */
  954.                 case 'O':
  955. #endif
  956.                 case 'y':    /* yes, substitute */
  957.                 case 'Y':
  958.                 case ' ':
  959.                     break;
  960.  
  961.                 case 'n':    /* no, onward */
  962.                 case 'N':
  963.                     forwchar(FALSE, 1);
  964.                     continue;
  965.  
  966.                 case '!':    /* yes/stop asking */
  967.                     kind = FALSE;
  968.                     break;
  969.  
  970.                 case 'u':    /* undo last and re-prompt */
  971.                 case 'U':
  972.                     /* Restore old position.
  973.                      */
  974.                     if (lastline == NULL) {
  975.                         /* There is nothing to undo.
  976.                          */
  977.                         TTbeep();
  978.                         goto pprompt;
  979.                     }
  980.                     curwp->w_dotp = lastline;
  981.                     curwp->w_doto = lastoff;
  982.                     lastline = NULL;
  983.                     lastoff = 0;
  984.  
  985.                     /* Delete the new string,
  986.                      * restore the old match.
  987.                      */
  988.                     backchar(FALSE, replen);
  989.                     status = delins(replen, oldpatmatch, FALSE);
  990.                     if (status != TRUE)
  991.                         return (status);
  992.  
  993.                     /* Record one less substitution,
  994.                      * backup, save our place, and
  995.                      * reprompt.
  996.                      */
  997.                     --numsub;
  998.                     backchar(FALSE, oldmatchlen);
  999.                     matchlen = oldmatchlen;
  1000.                     matchline = curwp->w_dotp;
  1001.                     matchoff  = curwp->w_doto;
  1002.                     continue;
  1003.  
  1004.                 case '.':    /* abort! and return */
  1005.                     /* restore old position */
  1006.                     curwp->w_dotp = origline;
  1007.                     curwp->w_doto = origoff;
  1008.                     curwp->w_flag |= WFMOVE;
  1009.  
  1010.                 case BELL:    /* abort! and stay */
  1011.                     mlwrite(TEXT89);
  1012. /*                        "Aborted!" */
  1013.                     return FALSE;
  1014.  
  1015.                 default:    /* bitch and beep */
  1016.                     TTbeep();
  1017.  
  1018.                 case '?':    /* help me */
  1019.                     mlwrite(TEXT90);
  1020. /*"(Y)es, (N)o, (!)Do rest, (U)ndo last, (^G)Abort, (.)Abort back, (?)Help: "*/
  1021.                     goto qprompt;
  1022.  
  1023.             }    /* end of switch */
  1024.         }    /* end of "if kind" */
  1025.  
  1026.         /* if this is the point origin, flag so we a can reset it */
  1027.         if (curwp->w_dotp == origline) {
  1028.             origline = NULL;
  1029.             lastline = curwp->w_dotp->l_bp;
  1030.         }
  1031.  
  1032.         /* Delete the sucker, and insert its
  1033.          * replacement.
  1034.          */
  1035. #if    MAGIC
  1036.         status = delins(matchlen, &rpat[0], rmagical);
  1037. #else
  1038.         status = delins(matchlen, &rpat[0], FALSE);
  1039. #endif
  1040.         if (origline == NULL) {
  1041.             origline = lastline->l_fp;
  1042.             origoff = 0;
  1043.         }
  1044.  
  1045.         if (status != TRUE)
  1046.             return (status);
  1047.  
  1048.         numsub++;    /* increment # of substitutions */
  1049.  
  1050.         /* Save our position, the match length, and the string
  1051.          * matched if we are query-replacing, as we may undo
  1052.          * the replacement. If we are not querying, check to
  1053.          * make sure that we didn't replace an empty string
  1054.          * (possible in MAGIC mode), because we'll infinite loop.
  1055.          */
  1056.         if (kind) {
  1057.             lastline = curwp->w_dotp;
  1058.             lastoff = curwp->w_doto;
  1059.             oldmatchlen = matchlen;    /* Save the length for un-do.*/
  1060.  
  1061.             if (oldpatmatch != NULL)
  1062.                 free(oldpatmatch);
  1063.  
  1064.             if ((oldpatmatch = malloc(matchlen + 1)) == NULL) {
  1065.                 mlwrite(TEXT94);
  1066. /*                    "%%Out of memory" */
  1067.                 return ABORT;
  1068.             }
  1069.             strcpy(oldpatmatch, patmatch);
  1070.         }
  1071.         else if (matchlen == 0) {
  1072.             mlwrite(TEXT91);
  1073. /*                "Empty string replaced, stopping." */
  1074.             return FALSE;
  1075.         }
  1076.     }
  1077.  
  1078.     /* And report the results.
  1079.      */
  1080.     mlwrite(TEXT92, numsub);
  1081. /*        "%d substitutions" */
  1082.     return (TRUE);
  1083. }
  1084.  
  1085. /*
  1086.  * delins -- Delete a specified length from the current point
  1087.  *    then either insert the string directly, or make use of
  1088.  *    replacement meta-array.
  1089.  */
  1090. int PASCAL NEAR delins(dlength, instr, use_rmc)
  1091. int    dlength;
  1092. char    *instr;
  1093. int    use_rmc;
  1094. {
  1095.     register int    status;
  1096.     register char    *rstr;
  1097. #if    MAGIC
  1098.     register RMC    *rmcptr;
  1099. #endif
  1100.  
  1101.     replen = 0;
  1102.  
  1103.     /* Zap what we gotta,
  1104.      * and insert its replacement.
  1105.      */
  1106.     if ((status = ldelete((long) dlength, FALSE)) != TRUE)
  1107.         mlwrite(TEXT93);
  1108. /*            "%%ERROR while deleting" */
  1109.     else
  1110. #if    MAGIC
  1111.         if (use_rmc && (curwp->w_bufp->b_mode & MDMAGIC)) {
  1112.             rmcptr = &rmcpat[0];
  1113.             while (rmcptr->mc_type != MCNIL && status == TRUE) {
  1114.                 if (rmcptr->mc_type == LITCHAR)
  1115.                     status = linstr(rstr = rmcptr->u.rstr);
  1116.                 else if (rmcptr->mc_type == DITTO)
  1117.                     status = linstr(rstr = patmatch);
  1118.                 else
  1119.                     status = linstr(rstr = fixnull(grpmatch[rmcptr->u.group_no]));
  1120.                 replen += strlen(rstr);
  1121.                 rmcptr++;
  1122.             }
  1123.         }
  1124.         else
  1125. #endif
  1126.         {
  1127.             status = linstr(instr);
  1128.             replen = strlen(instr);
  1129.         }
  1130.  
  1131.     return (status);
  1132. }
  1133.  
  1134. /*
  1135.  * expandp -- Expand control key sequences for output.
  1136.  */
  1137. int PASCAL NEAR expandp(srcstr, deststr, maxlength)
  1138. char    *srcstr;    /* string to expand */
  1139. char    *deststr;    /* destination of expanded string */
  1140. int    maxlength;    /* maximum chars in destination */
  1141. {
  1142.     unsigned char c;    /* current char to translate */
  1143.  
  1144.     /* Scan through the string.
  1145.      */
  1146.     while ((c = *srcstr++) != 0) {
  1147.         if (c == '\r')        /* it's a newline */
  1148.         {
  1149.             *deststr++ = '<';
  1150.             *deststr++ = 'N';
  1151.             *deststr++ = 'L';
  1152.             *deststr++ = '>';
  1153.             maxlength -= 4;
  1154.         }
  1155.         else if (c < 0x20 || c == 0x7f)    /* control character */
  1156.         {
  1157.             *deststr++ = '^';
  1158.             *deststr++ = c ^ 0x40;
  1159.             maxlength -= 2;
  1160.         }
  1161.         else if (c == '%') {
  1162.             *deststr++ = '%';
  1163.             *deststr++ = '%';
  1164.             maxlength -= 2;
  1165.         }
  1166.         else            /* any other character */
  1167.         {
  1168.             *deststr++ = c;
  1169.             maxlength--;
  1170.         }
  1171.  
  1172.         /* check for maxlength */
  1173.         if (maxlength < 4) {
  1174.             *deststr++ = '$';
  1175.             *deststr = '\0';
  1176.             return FALSE;
  1177.         }
  1178.     }
  1179.     *deststr = '\0';
  1180.     return (TRUE);
  1181. }
  1182.  
  1183. /*
  1184.  * boundry -- Return information depending on whether we may search no
  1185.  *    further.  Beginning of file and end of file are the obvious
  1186.  *    cases, but we may want to add further optional boundry restrictions
  1187.  *    in future, a' la VMS EDT.  At the moment, just return (TRUE) or
  1188.  *    FALSE depending on if a boundry is hit (ouch).
  1189.  */
  1190. int PASCAL NEAR boundry(curline, curoff, dir)
  1191. LINE    *curline;
  1192. int    curoff, dir;
  1193. {
  1194.     register int    border;
  1195.  
  1196.     if (dir == FORWARD) {
  1197.         border = (curoff == llength(curline)) &&
  1198.              (lforw(curline) == curbp->b_linep);
  1199.     }
  1200.     else
  1201.     {
  1202.         border = (curoff == 0) &&
  1203.              (lback(curline) == curbp->b_linep);
  1204.     }
  1205.     return (border);
  1206. }
  1207.  
  1208. /*
  1209.  * nextch -- retrieve the next/previous character in the buffer,
  1210.  *    and advance/retreat the point.
  1211.  *    The order in which this is done is significant, and depends
  1212.  *    upon the direction of the search.  Forward searches look at
  1213.  *    the current character and move, reverse searches move and
  1214.  *    look at the character.
  1215.  */
  1216. int PASCAL NEAR nextch(pcurline, pcuroff, dir)
  1217. LINE    **pcurline;
  1218. int    *pcuroff;
  1219. int    dir;
  1220. {
  1221.     register LINE    *curline;
  1222.     register int    curoff;
  1223.     register int    c;
  1224.  
  1225.     curline = *pcurline;
  1226.     curoff = *pcuroff;
  1227.  
  1228.     if (dir == FORWARD) {
  1229.         if (curoff == llength(curline))        /* if at EOL */
  1230.         {
  1231.             curline = lforw(curline);    /* skip to next line */
  1232.             curoff = 0;
  1233.             c = '\r';            /* and return a <NL> */
  1234.         }
  1235.         else
  1236.             c = lgetc(curline, curoff++);    /* get the char */
  1237.     }
  1238.     else            /* Reverse.*/
  1239.     {
  1240.         if (curoff == 0) {
  1241.             curline = lback(curline);
  1242.             curoff = llength(curline);
  1243.             c = '\r';
  1244.         }
  1245.         else
  1246.             c = lgetc(curline, --curoff);
  1247.  
  1248.     }
  1249.     *pcurline = curline;
  1250.     *pcuroff = curoff;
  1251.  
  1252.     return (c);
  1253. }
  1254.  
  1255. #if    MAGIC
  1256. /*
  1257.  * mcstr -- Set up the 'magic' array.  The closure symbol is taken as
  1258.  *    a literal character when (1) it is the first character in the
  1259.  *    pattern, and (2) when preceded by a symbol that does not allow
  1260.  *    closure, such as beginning or end of line symbol, or another
  1261.  *    closure symbol.
  1262.  *
  1263.  *    Coding comment (jmg):  yes, i know i have gotos that are, strictly
  1264.  *    speaking, unnecessary.  But right now we are so cramped for
  1265.  *    code space that i will grab what i can in order to remain
  1266.  *    within the 64K limit.  C compilers actually do very little
  1267.  *    in the way of optimizing - they expect you to do that.
  1268.  */
  1269. int PASCAL NEAR mcstr()
  1270. {
  1271.     MC    *mcptr, *rtpcm;
  1272.     char    *patptr;
  1273.     int    pchr;
  1274.     int    status = TRUE;
  1275.     int    does_closure = FALSE;
  1276.     int    mj = 0;
  1277.     int    group_stack[MAXGROUPS];
  1278.     int    stacklevel = 0;
  1279.  
  1280.     /* If we had metacharacters in the MC array previously,
  1281.      * free up any bitmaps that may have been allocated, and
  1282.      * reset magical.
  1283.      */
  1284.     if (magical)
  1285.         mcclear();
  1286.  
  1287.     mcptr = &mcpat[0];
  1288.     patptr = &pat[0];
  1289.  
  1290.     while ((pchr = *patptr) && status) {
  1291.         switch (pchr) {
  1292.             case MC_CCL:
  1293.                 status = cclmake(&patptr, mcptr);
  1294.                 magical = TRUE;
  1295.                 does_closure = TRUE;
  1296.                 break;
  1297.  
  1298.             case MC_BOL:
  1299.                 /* If the BOL character isn't the
  1300.                  * first in the pattern, we assume
  1301.                  * it's a literal instead.
  1302.                  */
  1303.                 if (mj != 0)
  1304.                     goto litcase;
  1305.  
  1306.                 mcptr->mc_type = BOL;
  1307.                 magical = TRUE;
  1308.                 break;
  1309.  
  1310.             case MC_EOL:
  1311.                 /* If the EOL character isn't the
  1312.                  * last in the pattern, we assume
  1313.                  * it's a literal instead.
  1314.                  */
  1315.                 if (*(patptr + 1) != '\0')
  1316.                     goto litcase;
  1317.  
  1318.                 mcptr->mc_type = EOL;
  1319.                 magical = TRUE;
  1320.                 break;
  1321.  
  1322.             case MC_ANY:
  1323.                 mcptr->mc_type = ANY;
  1324.                 magical = TRUE;
  1325.                 does_closure = TRUE;
  1326.                 break;
  1327.  
  1328.             case MC_CLOSURE:
  1329.             case MC_CLOSURE_1:
  1330.             case MC_ZEROONE:
  1331.  
  1332.                 /* Does the closure symbol mean closure here?
  1333.                  * If so, back up to the previous element
  1334.                  * and indicate it is enclosed.
  1335.                  */
  1336.                 if (does_closure == FALSE)
  1337.                     goto litcase;
  1338.                 mj--;
  1339.                 mcptr--;
  1340.                 if (pchr == MC_CLOSURE)
  1341.                     mcptr->mc_type |= CLOSURE;
  1342.                 else if (pchr == MC_CLOSURE_1)
  1343.                     mcptr->mc_type |= CLOSURE_1;
  1344.                 else
  1345.                     mcptr->mc_type |= ZEROONE;
  1346.  
  1347.                 magical = TRUE;
  1348.                 does_closure = FALSE;
  1349.                 break;
  1350.  
  1351.             case MC_ESC:
  1352.  
  1353.                 /* No break between here and LITCHAR if
  1354.                  * the next character is to be taken literally.
  1355.                  */
  1356.                 magical = TRUE;
  1357.                 pchr = *++patptr;
  1358.                 if (pchr == MC_GRPBEG) {
  1359.                     /* Start of a group.  Indicate it, and
  1360.                      * set magical.
  1361.                      */
  1362.                     if (++group_count < MAXGROUPS) {
  1363.                         mcptr->mc_type = GRPBEG;
  1364.                         mcptr->u.group_no = group_count;
  1365.                         group_stack[stacklevel++] = group_count;
  1366.                         does_closure = FALSE;
  1367.                     }
  1368.                     else
  1369.                     {
  1370.                         mlwrite(TEXT221);
  1371. /*                            "Too many groups" */
  1372.                         status = FALSE;
  1373.                     }
  1374.                     break;
  1375.                 }
  1376.                 else if (pchr == MC_GRPEND) {
  1377.                     /* If we've no groups to close, assume
  1378.                      * a literal character.  Otherwise,
  1379.                      * indicate the end of a group.
  1380.                      */
  1381.                     if (stacklevel > 0) {
  1382.                         mcptr->u.group_no = group_stack[--stacklevel];
  1383.                         mcptr->mc_type = GRPEND;
  1384.                         does_closure = FALSE;
  1385.                         break;
  1386.                     }
  1387.                 }
  1388.                 else if (pchr == '\0') {
  1389.                     pchr = '\\';
  1390.                     --patptr;
  1391.                 }
  1392.             default:
  1393. litcase:            mcptr->mc_type = LITCHAR;
  1394.                 mcptr->u.lchar = pchr;
  1395.                 does_closure = TRUE;
  1396.                 break;
  1397.         }        /* End of switch.*/
  1398.         mcptr++;
  1399.         patptr++;
  1400.         mj++;
  1401.     }        /* End of while.*/
  1402.  
  1403.     /* Close off the meta-string, then set up the reverse array,
  1404.      * if the status is good.
  1405.      *
  1406.      * The beginning and end of groups are reversed by flipping
  1407.      * the bits, if they are set.
  1408.      *
  1409.      * If the status is not good, nil out the meta-pattern.
  1410.      * Even if the status is bad from the cclmake() routine,
  1411.      * the bitmap for that member is guaranteed to be freed.
  1412.      * So we stomp a MCNIL value there, and call mcclear()
  1413.      * to free any other bitmaps.
  1414.      *
  1415.      * Please note the structure assignment - your compiler may
  1416.      * not like that.
  1417.      */
  1418.     mcptr->mc_type = MCNIL;
  1419.     if (stacklevel) {
  1420.         status = FALSE;
  1421.         mlwrite(TEXT222);
  1422. /*            "Group not ended"    */
  1423.     }
  1424.  
  1425.     if (status) {
  1426.         rtpcm = &tapcm[0];
  1427.         while (--mj >= 0) {
  1428. #if    LATTICE
  1429.             movmem(--mcptr, rtpcm, sizeof (MC));
  1430. #else
  1431.             *rtpcm = *--mcptr;
  1432. #endif
  1433.             if (rtpcm->mc_type & (GRPBEG | GRPEND))
  1434.                 rtpcm->mc_type ^= (GRPBEG | GRPEND);
  1435.             rtpcm++;
  1436.         }
  1437.         rtpcm->mc_type = MCNIL;
  1438.     }
  1439.     else
  1440.     {
  1441.         (--mcptr)->mc_type = MCNIL;
  1442.         mcclear();
  1443.     }
  1444.  
  1445.     return (status);
  1446. }
  1447.  
  1448. /*
  1449.  * rmcstr -- Set up the replacement 'magic' array.  Note that if there
  1450.  *    are no meta-characters encountered in the replacement string,
  1451.  *    the array is never actually created - we will just use the
  1452.  *    character array rpat[] as the replacement string.
  1453.  */
  1454. int PASCAL NEAR rmcstr()
  1455. {
  1456.     RMC    *rmcptr;
  1457.     char    *patptr;
  1458.     int    pchr;
  1459.     int    status = TRUE;
  1460.     int    mj;
  1461.  
  1462.     patptr = &rpat[0];
  1463.     rmcptr = &rmcpat[0];
  1464.     mj = 0;
  1465.     rmagical = FALSE;
  1466.  
  1467.     while (*patptr && status == TRUE) {
  1468.         switch (*patptr) {
  1469.             case MC_DITTO:
  1470.  
  1471.                 /* If there were non-magical characters in
  1472.                  * the string before reaching this character
  1473.                  * plunk it in the replacement array before
  1474.                  * processing the current meta-character.
  1475.                  */
  1476.                 if (mj != 0) {
  1477.                     rmcptr->mc_type = LITCHAR;
  1478.                     if ((rmcptr->u.rstr = malloc(mj + 1)) == NULL) {
  1479.                         mlwrite(TEXT94);
  1480. /*                            "%%Out of memory" */
  1481.                         status = FALSE;
  1482.                         break;
  1483.                     }
  1484.                     bytecopy(rmcptr->u.rstr, patptr - mj, mj);
  1485.                     rmcptr++;
  1486.                     mj = 0;
  1487.                 }
  1488.                 rmcptr->mc_type = DITTO;
  1489.                 rmcptr++;
  1490.                 rmagical = TRUE;
  1491.                 break;
  1492.  
  1493.             case MC_ESC:
  1494.                 pchr = *(patptr + 1);    /* peek at next char.*/
  1495.                 if (pchr <= '9' && pchr >= '1') {
  1496.                     if (mj != 0) {
  1497.                         rmcptr->mc_type = LITCHAR;
  1498.                         if ((rmcptr->u.rstr = malloc(mj + 1)) == NULL) {
  1499.                             mlwrite(TEXT94);
  1500. /*                            "%%Out of memory" */
  1501.                             status = FALSE;
  1502.                             break;
  1503.                         }
  1504.                         bytecopy(rmcptr->u.rstr, patptr - mj, mj);
  1505.                         rmcptr++;
  1506.                         mj = 0;
  1507.                     }
  1508.                     rmcptr->mc_type = GROUP;
  1509.                     rmcptr->u.group_no = pchr - '0';
  1510.                     patptr++;
  1511.                 }
  1512.                 else
  1513.                 {
  1514.                     rmcptr->mc_type = LITCHAR;
  1515.  
  1516.                     /* We malloc mj plus two here, instead
  1517.                      * of one, because we have to count the
  1518.                      * current character.
  1519.                      */
  1520.                     if ((rmcptr->u.rstr = malloc(mj + 2)) == NULL) {
  1521.                         mlwrite(TEXT94);
  1522. /*                        "%%Out of memory" */
  1523.                         status = FALSE;
  1524.                         break;
  1525.                     }
  1526.  
  1527.                     bytecopy(rmcptr->u.rstr, patptr - mj, mj + 1);
  1528.  
  1529.                     /* If MC_ESC is not the last character
  1530.                      * in the string, find out what it is
  1531.                      * escaping, and overwrite the last
  1532.                      * character with it.
  1533.                      */
  1534.                     if (pchr != '\0') {
  1535.                         *((rmcptr->u.rstr) + mj) = pchr;
  1536.                         patptr++;
  1537.                     }
  1538.                     mj = 0;
  1539.                 }
  1540.  
  1541.                 rmcptr++;
  1542.                 rmagical = TRUE;
  1543.                 break;
  1544.  
  1545.             default:
  1546.                 mj++;
  1547.         }
  1548.         patptr++;
  1549.     }
  1550.  
  1551.     if (rmagical && mj > 0) {
  1552.         rmcptr->mc_type = LITCHAR;
  1553.         if ((rmcptr->u.rstr = malloc(mj + 1)) == NULL) {
  1554.             mlwrite(TEXT94);
  1555. /*                "%%Out of memory" */
  1556.             status = FALSE;
  1557.         }
  1558.         bytecopy(rmcptr->u.rstr, patptr - mj, mj);
  1559.         rmcptr++;
  1560.     }
  1561.  
  1562.     rmcptr->mc_type = MCNIL;
  1563.     return (status);
  1564. }
  1565.  
  1566. /*
  1567.  * mcclear -- Free up any CCL bitmaps, and MCNIL the MC search arrays.
  1568.  */
  1569. VOID PASCAL NEAR mcclear()
  1570. {
  1571.     register MC    *mcptr;
  1572.     register int    j;
  1573.  
  1574.     mcptr = &mcpat[0];
  1575.  
  1576.     while (mcptr->mc_type != MCNIL) {
  1577.         if ((mcptr->mc_type == CCL) || (mcptr->mc_type == NCCL))
  1578.             free(mcptr->u.cclmap);
  1579.         mcptr++;
  1580.     }
  1581.     mcpat[0].mc_type = tapcm[0].mc_type = MCNIL;
  1582.  
  1583.     /*
  1584.      * Remember that grpmatch[0] == patmatch.
  1585.      */
  1586.     for (j = 0; j < MAXGROUPS; j++) {
  1587.         if (grpmatch[j] != NULL) {
  1588.             free(grpmatch[j]);
  1589.             grpmatch[j] = NULL;
  1590.         }
  1591.     }
  1592.     patmatch = NULL;
  1593.     group_count = 0;
  1594.     magical = FALSE;
  1595. }
  1596.  
  1597. /*
  1598.  * rmcclear -- Free up any strings, and MCNIL the RMC array.
  1599.  */
  1600. VOID PASCAL NEAR rmcclear()
  1601. {
  1602.     register RMC    *rmcptr;
  1603.     register int    j;
  1604.  
  1605.     rmcptr = &rmcpat[0];
  1606.  
  1607.     while (rmcptr->mc_type != MCNIL) {
  1608.         if (rmcptr->mc_type == LITCHAR)
  1609.             free(rmcptr->u.rstr);
  1610.         rmcptr++;
  1611.     }
  1612.  
  1613.     rmcpat[0].mc_type = MCNIL;
  1614. }
  1615.  
  1616. /*
  1617.  * mceq -- meta-character equality with a character.  In Kernighan & Plauger's
  1618.  *    Software Tools, this is the function omatch(), but i felt there were
  1619.  *    too many functions with the 'match' name already.
  1620.  */
  1621. int PASCAL NEAR    mceq(bc, mt)
  1622. int    bc;
  1623. MC    *mt;
  1624. {
  1625.     register int result;
  1626.  
  1627.     switch (mt->mc_type & MASKCLO) {
  1628.         case LITCHAR:
  1629.             result = eq(bc, (int) mt->u.lchar);
  1630.             break;
  1631.  
  1632.         case ANY:
  1633.             result = (bc != '\r');
  1634.             break;
  1635.  
  1636.         case CCL:
  1637.             if (!(result = biteq(bc, mt->u.cclmap))) {
  1638.                 if ((curwp->w_bufp->b_mode & MDEXACT) == 0 &&
  1639.                     (isletter(bc))) {
  1640.                     result = biteq(chcase(bc), mt->u.cclmap);
  1641.                 }
  1642.             }
  1643.             break;
  1644.  
  1645.         case NCCL:
  1646.             result = !biteq(bc, mt->u.cclmap);
  1647.  
  1648.             if ((curwp->w_bufp->b_mode & MDEXACT) == 0 &&
  1649.                 (isletter(bc))) {
  1650.                 result &= !biteq(chcase(bc), mt->u.cclmap);
  1651.             }
  1652.             break;
  1653.  
  1654.         default:
  1655.             mlwrite(TEXT95, mt->mc_type);
  1656. /*                "%%mceq: what is %d?" */
  1657.             result = FALSE;
  1658.             break;
  1659.  
  1660.     }    /* End of switch.*/
  1661.  
  1662.     return (result);
  1663. }
  1664.  
  1665. /*
  1666.  * cclmake -- create the bitmap for the character class.
  1667.  *    ppatptr is left pointing to the end-of-character-class character,
  1668.  *    so that a loop may automatically increment with safety.
  1669.  */
  1670. int PASCAL NEAR    cclmake(ppatptr, mcptr)
  1671. char    **ppatptr;
  1672. MC    *mcptr;
  1673. {
  1674.     BITMAP        bmap;
  1675.     register char    *patptr;
  1676.     register int    pchr, ochr;
  1677.  
  1678. #if    OS2
  1679.     if ((bmap = (BITMAP) malloc(HIBYTE_)) == NULL) {
  1680.         mlwrite(TEXT94);
  1681. /*            "%%Out of memory" */
  1682.         return FALSE;
  1683.     }
  1684.  
  1685.     memset(bmap, 0, HIBYTE_);
  1686. #else
  1687.     if ((bmap = (BITMAP) malloc(HIBYTE)) == NULL) {
  1688.         mlwrite(TEXT94);
  1689. /*            "%%Out of memory" */
  1690.         return FALSE;
  1691.     }
  1692.  
  1693.     memset(bmap, 0, HIBYTE);
  1694. #endif
  1695.  
  1696.     mcptr->u.cclmap = bmap;
  1697.     patptr = *ppatptr;
  1698.     ochr = MC_CCL;
  1699.  
  1700.     /*
  1701.      * Test the initial character(s) in ccl for
  1702.      * special cases - negate ccl, or an end ccl
  1703.      * character as a first character.  Anything
  1704.      * else gets set in the bitmap.
  1705.      */
  1706.     if (*++patptr == MC_NCCL) {
  1707.         patptr++;
  1708.         mcptr->mc_type = NCCL;
  1709.     }
  1710.     else
  1711.         mcptr->mc_type = CCL;
  1712.  
  1713.     if ((pchr = *patptr) == MC_ECCL) {
  1714.         mlwrite(TEXT96);
  1715. /*            "%%No characters in character class" */
  1716.         free(bmap);
  1717.         return FALSE;
  1718.     }
  1719.  
  1720.     while (pchr != MC_ECCL && pchr != '\0') {
  1721.         switch (pchr) {
  1722.             /* Range character loses its meaning if it is
  1723.              * the first or last character in the class.  We
  1724.              * also treat it as un-ordinary if the order is
  1725.              * wrong, e.g. "z-a".
  1726.              */
  1727.             case MC_RCCL:
  1728.                 pchr = *(patptr + 1);
  1729.                 if (ochr == MC_CCL || pchr == MC_ECCL ||
  1730.                     ochr > pchr)
  1731.                     setbit(MC_RCCL, bmap);
  1732.                 else
  1733.                 {
  1734.                     do {
  1735.                         setbit(++ochr, bmap);
  1736.                     } while (ochr < pchr);
  1737.                     patptr++;
  1738.                 }
  1739.                 break;
  1740.  
  1741.             /* Note: no break between case MC_ESC and the default.
  1742.              */
  1743.             case MC_ESC:
  1744.                 pchr = *++patptr;
  1745.             default:
  1746.                 setbit(pchr, bmap);
  1747.                 break;
  1748.         }
  1749.         ochr = pchr;
  1750.         pchr = *++patptr;
  1751.     }
  1752.  
  1753.     *ppatptr = patptr;
  1754.  
  1755.     if (pchr == '\0') {
  1756.         mlwrite(TEXT97);
  1757. /*            "%%Character class not ended" */
  1758.         free(bmap);
  1759.         return FALSE;
  1760.     }
  1761.     return TRUE;
  1762. }
  1763.  
  1764. /*
  1765.  * biteq -- is the character in the bitmap?
  1766.  */
  1767. int PASCAL NEAR    biteq(bc, cclmap)
  1768. int    bc;
  1769. BITMAP    cclmap;
  1770. {
  1771.     if (bc >= HICHAR)
  1772.         return FALSE;
  1773.  
  1774.     return ( (*(cclmap + (bc >> 3)) & BIT(bc & 7))? TRUE: FALSE );
  1775. }
  1776.  
  1777. /*
  1778.  * setbit -- Set a bit (ON only) in the bitmap.
  1779.  */
  1780. VOID PASCAL NEAR setbit(bc, cclmap)
  1781. int    bc;
  1782. BITMAP    cclmap;
  1783. {
  1784.     if (bc < HICHAR)
  1785.         *(cclmap + (bc >> 3)) |= BIT(bc & 7);
  1786. }
  1787. #endif
  1788.