home *** CD-ROM | disk | FTP | other *** search
/ Frozen Fish 1: Amiga / FrozenFish-Apr94.iso / bbs / alib / d3xx / d352 / mg.lha / MG / src.LZH / mg / re_search.c < prev    next >
C/C++ Source or Header  |  1990-05-23  |  16KB  |  702 lines

  1. /*
  2.  * regular expression search commands for MicroGnuEmacs
  3.  * 
  4.  * This file contains functions to implement several of gnuemacs' regular
  5.  * expression functions for MicroGnuEmacs.  Several of the routines below are
  6.  * just minor rearrangements of the MicroGnuEmacs non-regular expression
  7.  * search functions.  Hence some of them date back in essential structure to
  8.  * the original MicroEMACS; others are modifications of Rich Ellison's code.
  9.  * I, Peter Newton, wrote about half from scratch.
  10.  * 
  11.  * Although I have nothing to do with the GNU project, these functions require
  12.  * the GNU project's regular expression package (files regex.c and regex.h).
  13.  * Hence, this file comes under the same copyright notice as the GNU
  14.  * project's code.  As far as I know, the rest of MicroGnuEmacs need not
  15.  * since it may be used independently of any GNU project code.     In any case,
  16.  * I certainly do not warrant either the correctness or utility of this code.
  17.  * The GNU project copyright notice follows.  Don't you wish they would make
  18.  * it a bit shorter!
  19.  * 
  20.  * Note: the above paragraph appears to be in error; just because it uses the
  21.  * GNU regex code does not mean that this code must also be distributed under
  22.  * the GNU General Public License. That is only true if it actually
  23.  * incorporates part of the GNU code. Since Peter Newton put the GNU
  24.  * copyright on it, that may be the case. In any case, I can't in good
  25.  * conscience delete the GNU copyright, since Peter released the code that
  26.  * way originally.
  27.  */
  28.  
  29. /*
  30.  * Copyright (C) 1985 Richard M. Stallman
  31.  * 
  32.  * This software may be redistributed under the terms described in the file
  33.  * LICENSE in this directory.
  34.  */
  35.  
  36. #include "regexp.h"
  37.  
  38. #ifdef    REGEXP
  39. #include    "def.h"
  40. #include    "line.h"
  41. #include    "buffer.h"
  42. #include    "window.h"
  43.  
  44. #ifdef    ANSI
  45. #include <string.h>
  46. #include <stdlib.h>
  47. #endif
  48.  
  49. #ifndef    NO_PROTO
  50. static int re_doreplace PROTO((RSIZE, char *, int));
  51. static int re_forwsrch PROTO((VOID));
  52. static int re_backsrch PROTO((VOID));
  53. static int re_readpattern PROTO((char *));
  54. static int killmatches PROTO((int));
  55. static int countmatches PROTO((int));
  56. #endif
  57.  
  58. #define SRCH_BEGIN    (0)    /* Search sub-codes.     */
  59. #define SRCH_FORW    (-1)
  60. #define SRCH_BACK    (-2)
  61. #define SRCH_NOPR    (-3)
  62. #define SRCH_ACCM    (-4)
  63. #define SRCH_MARK    (-5)
  64.  
  65. char            re_pat[NPAT];    /* Regex pattern         */
  66. int             re_srch_lastdir = SRCH_NOPR;    /* Last search flags. */
  67. int             casefoldsearch = TRUE;    /* Does search ignore case ? */
  68.  
  69. /* Indexed by a character, gives the upper case equivalent of the character */
  70.  
  71. static char     upcase[0400] =
  72.     {000,  001,  002,  003,  004,  005,  006,  007,
  73.      010,  011,  012,  013,  014,  015,  016,  017,
  74.      020,  021,  022,  023,  024,  025,  026,  027,
  75.      030,  031,  032,  033,  034,  035,  036,  037,
  76.      040,  041,  042,  043,  044,  045,  046,  047,
  77.      050,  051,  052,  053,  054,  055,  056,  057,
  78.      060,  061,  062,  063,  064,  065,  066,  067,
  79.      070,  071,  072,  073,  074,  075,  076,  077,
  80.     0100, 0101, 0102, 0103, 0104, 0105, 0106, 0107,
  81.     0110, 0111, 0112, 0113, 0114, 0115, 0116, 0117,
  82.     0120, 0121, 0122, 0123, 0124, 0125, 0126, 0127,
  83.     0130, 0131, 0132, 0133, 0134, 0135, 0136, 0137,
  84.     0140, 0101, 0102, 0103, 0104, 0105, 0106, 0107,
  85.     0110, 0111, 0112, 0113, 0114, 0115, 0116, 0117,
  86.     0120, 0121, 0122, 0123, 0124, 0125, 0126, 0127,
  87.     0130, 0131, 0132, 0173, 0174, 0175, 0176, 0177,
  88.     0200, 0201, 0202, 0203, 0204, 0205, 0206, 0207,
  89.     0210, 0211, 0212, 0213, 0214, 0215, 0216, 0217,
  90.     0220, 0221, 0222, 0223, 0224, 0225, 0226, 0227,
  91.     0230, 0231, 0232, 0233, 0234, 0235, 0236, 0237,
  92.     0240, 0241, 0242, 0243, 0244, 0245, 0246, 0247,
  93.     0250, 0251, 0252, 0253, 0254, 0255, 0256, 0257,
  94.     0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267,
  95.     0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277,
  96.     0300, 0301, 0302, 0303, 0304, 0305, 0306, 0307,
  97.     0310, 0311, 0312, 0313, 0314, 0315, 0316, 0317,
  98.     0320, 0321, 0322, 0323, 0324, 0325, 0326, 0327,
  99.     0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337,
  100.     0340, 0341, 0342, 0343, 0344, 0345, 0346, 0347,
  101.     0350, 0351, 0352, 0353, 0354, 0355, 0356, 0357,
  102.     0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367,
  103.     0370, 0371, 0372, 0373, 0374, 0375, 0376, 0377
  104. };
  105.  
  106. /*
  107.  * Search forward. Get a search string from the user, and search for it,
  108.  * starting at ".". If found, "." gets moved to just after the matched
  109.  * characters, and display does all the hard stuff. If not found, it just
  110.  * prints a message.
  111.  */
  112. /* ARGSUSED */
  113. re_forwsearch(f, n)
  114. {
  115.     register int    s;
  116.  
  117.     if ((s = re_readpattern("RE Search")) != TRUE)
  118.         return (s);
  119.     if (re_forwsrch() == FALSE) {
  120.         ewprintf("Search failed: \"%s\"", re_pat);
  121.         return (FALSE);
  122.     }
  123.     re_srch_lastdir = SRCH_FORW;
  124.     return (TRUE);
  125. }
  126.  
  127. /*
  128.  * Reverse search. Get a search string from the     user, and search, starting
  129.  * at "." and proceeding toward the front of the buffer. If found "." is left
  130.  * pointing at the first character of the pattern [the last character that
  131.  * was matched].
  132.  */
  133. /* ARGSUSED */
  134. re_backsearch(f, n)
  135. {
  136.     register int    s;
  137.  
  138.     if ((s = re_readpattern("RE Search backward")) != TRUE)
  139.         return (s);
  140.     if (re_backsrch() == FALSE) {
  141.         ewprintf("Search failed: \"%s\"", re_pat);
  142.         return (FALSE);
  143.     }
  144.     re_srch_lastdir = SRCH_BACK;
  145.     return (TRUE);
  146. }
  147.  
  148.  
  149.  
  150. /*
  151.  * Search again, using the same search string and direction as the last
  152.  * search command. The direction has been saved in "srch_lastdir", so you
  153.  * know which way to go.
  154.  */
  155. /* ARGSUSED */
  156. /*
  157.  * This code has problems-- some incompatibility(?) with extend.c causes
  158.  * match to fail when it should not.
  159.  */
  160. re_searchagain(f, n)
  161. {
  162.  
  163.     if (re_srch_lastdir == SRCH_NOPR) {
  164.         ewprintf("No last search");
  165.         return (FALSE);
  166.     }
  167.     if (re_srch_lastdir == SRCH_FORW) {
  168.         if (re_forwsrch() == FALSE) {
  169.             ewprintf("Search failed: \"%s\"", re_pat);
  170.             return (FALSE);
  171.         }
  172.         return (TRUE);
  173.     }
  174.     if (re_srch_lastdir == SRCH_BACK) {
  175.         if (re_backsrch() == FALSE) {
  176.             ewprintf("Search failed: \"%s\"", re_pat);
  177.             return (FALSE);
  178.         }
  179.         return (TRUE);
  180.     }
  181. }
  182.  
  183.  
  184. #include "regex.h"
  185. #define BYTEWIDTH 8
  186.  
  187. /* Compiled regex goes here-- changed only when new pattern read */
  188. static struct re_pattern_buffer re_buff;
  189. static char     fastmap[(1 << BYTEWIDTH)];
  190.  
  191. /* regs holds boundaries of matched text */
  192. static struct re_registers regs;
  193.  
  194. /*
  195.  * Re-Query Replace. Replace strings selectively.  Does a search and replace
  196.  * operation.
  197.  */
  198. /* ARGSUSED */
  199. re_queryrepl(f, n)
  200. {
  201.     register int    s;
  202.     register int    rcnt = 0;    /* Replacements made so far     */
  203.     register int    plen;    /* length of found string     */
  204.     char            news[NPAT];    /* replacement string         */
  205.  
  206.     /* Casefold check */
  207.     if (!casefoldsearch)
  208.         f = TRUE;
  209.  
  210.     if ((s = re_readpattern("RE Query replace")) != TRUE)
  211.         return (s);
  212.     if ((s = ereply("Query replace %s with: ", news, NPAT, re_pat)) == ABORT)
  213.         return (s);
  214.     if (s == FALSE)
  215.         news[0] = '\0';
  216.     ewprintf("Query replacing %s with %s:", re_pat, news);
  217.  
  218.     /*
  219.      * Search forward repeatedly, checking each time whether to insert or
  220.      * not.  The "!" case makes the check always true, so it gets put
  221.      * into a tighter loop for efficiency.
  222.      */
  223.  
  224.     while (re_forwsrch() == TRUE) {
  225. retry:
  226.         update();
  227.         switch (getkey(DISSCR)) {
  228.         case ' ':
  229.             plen = regs.end[0] - regs.start[0];
  230.             if (re_doreplace((RSIZE) plen, news, f) == FALSE)
  231.                 return (FALSE);
  232.             rcnt++;
  233.             break;
  234.  
  235.         case '.':
  236.             plen = regs.end[0] - regs.start[0];
  237.             if (re_doreplace((RSIZE) plen, news, f) == FALSE)
  238.                 return (FALSE);
  239.             rcnt++;
  240.             goto stopsearch;
  241.  
  242.         case CCHR('G'):/* ^G */
  243.             (VOID) ctrlg(FFRAND, 0);
  244.         case CCHR('['):/* ESC */
  245.         case '`':
  246.             goto stopsearch;
  247.  
  248.         case '!':
  249.             do {
  250.                 plen = regs.end[0] - regs.start[0];
  251.                 if (re_doreplace((RSIZE) plen, news, f) == FALSE)
  252.                     return (FALSE);
  253.                 rcnt++;
  254.             } while (re_forwsrch() == TRUE);
  255.             goto stopsearch;
  256.  
  257.         case CCHR('?'):/* To not replace */
  258.             break;
  259.  
  260.         default:
  261.             ewprintf("<SP> replace, [.] rep-end, <DEL> don't, [!] repl rest <ESC> quit");
  262.             goto retry;
  263.         }
  264.     }
  265. stopsearch:
  266.     curwp->w_flag |= WFHARD;
  267.     update();
  268.     if (rcnt == 0)
  269.         ewprintf("(No replacements done)");
  270.     else if (rcnt == 1)
  271.         ewprintf("(1 replacement done)");
  272.     else
  273.         ewprintf("(%d replacements done)", rcnt);
  274.     return TRUE;
  275. }
  276.  
  277.  
  278.  
  279. /*
  280.  * Routine re_doreplace calls lreplace to make replacements needed by
  281.  * re_query replace.  Its reason for existence is to deal with \1, \2. etc.
  282.  */
  283.  
  284. /* Maximum length of replacement string */
  285. #define REPLEN 256
  286.  
  287. static
  288. re_doreplace(plen, st, f)
  289.     register RSIZE  plen;    /* length to remove         */
  290.     char           *st;    /* replacement string         */
  291.     int             f;    /* case hack disable         */
  292. {
  293.     int             s;
  294.     int             num, k;
  295.     register int    j;
  296.     int             more, state;
  297.     struct line    *clp;
  298.     char            repstr[REPLEN];
  299.  
  300.     clp = curwp->w_dotp;
  301.     more = TRUE;
  302.     j = 0;
  303.     state = 0;
  304.  
  305.     /* The following FSA parses the replacement string */
  306.     while (more) {
  307.         switch (state) {
  308.  
  309.         case 0:
  310.             if (*st == '\\') {
  311.                 st++;
  312.                 state = 1;
  313.             } else if (*st == '\0')
  314.                 more = FALSE;
  315.             else {
  316.                 repstr[j] = *st;
  317.                 j++;
  318.                 if (j >= REPLEN)
  319.                     return (FALSE);
  320.                 st++;
  321.             }
  322.             break;
  323.         case 1:
  324.             if (*st >= '0' && *st <= '9') {
  325.                 num = *st - '0';
  326.                 st++;
  327.                 state = 2;
  328.             } else if (*st == '\0')
  329.                 more = FALSE;
  330.             else {
  331.                 repstr[j] = *st;
  332.                 j++;
  333.                 if (j >= REPLEN)
  334.                     return (FALSE);
  335.                 st++;
  336.                 state = 0;
  337.             }
  338.             break;
  339.         case 2:
  340.             if (*st >= '0' && *st <= '9') {
  341.                 num = 10 * num + *st - '0';
  342.                 st++;
  343.             } else {
  344.                 if (num >= RE_NREGS)
  345.                     return (FALSE);
  346.                 k = regs.end[num] - regs.start[num];
  347.                 if (j + k >= REPLEN)
  348.                     return (FALSE);
  349.                 bcopy(&(clp->l_text[regs.start[num]]), &repstr[j], k);
  350.                 j += k;
  351.                 if (*st == '\0')
  352.                     more = FALSE;
  353.                 if (*st == '\\') {
  354.                     st++;
  355.                     state = 1;
  356.                 } else {
  357.                     repstr[j] = *st;
  358.                     j++;
  359.                     if (j >= REPLEN)
  360.                         return (FALSE);
  361.                     st++;
  362.                     state = 0;
  363.                 }
  364.             }
  365.             break;
  366.         }        /* end case */
  367.     }            /* end while */
  368.  
  369.     repstr[j] = '\0';
  370.  
  371.     s = lreplace(plen, repstr, f);
  372.  
  373.     return (s);
  374. }
  375.  
  376.  
  377.  
  378. /*
  379.  * This routine does the real work of a forward search. The pattern is
  380.  * sitting in the external variable "pat". If found, dot is updated, the
  381.  * window system is notified of the change, and TRUE is returned. If the
  382.  * string isn't found, FALSE is returned.
  383.  */
  384. static
  385. re_forwsrch()
  386. {
  387.  
  388.     register struct line *clp;
  389.     register int    tbo;
  390.     int             ntries;
  391.     int             i;
  392.  
  393.     clp = curwp->w_dotp;
  394.     tbo = curwp->w_doto;
  395.  
  396.     if (tbo == clp->l_used)
  397.         /*
  398.          * Don't start matching off end of line-- must move to
  399.          * beginning of next line, unless at end
  400.          */
  401.         if (clp != curbp->b_linep) {
  402.             clp = lforw(clp);
  403.             tbo = 0;
  404.         }
  405.     /*
  406.      * Note this loop does not process the last line, but this editor
  407.      * always makes the last line empty so this is good.
  408.      */
  409.  
  410.     while (clp != (curbp->b_linep)) {
  411.  
  412.         ntries = llength(clp) - tbo;
  413.         i = re_search(&re_buff, ltext(clp), llength(clp), tbo, ntries, ®s);
  414.  
  415.         if (i == -1) {
  416.             clp = lforw(clp);
  417.             tbo = 0;
  418.         } else {
  419.             curwp->w_doto = regs.end[0];
  420.             curwp->w_dotp = clp;
  421.             curwp->w_flag |= WFMOVE;
  422.             return (TRUE);
  423.         }
  424.  
  425.     }
  426.  
  427.     return (FALSE);
  428.  
  429. }
  430.  
  431.  
  432. /*
  433.  * This routine does the real work of a backward search. The pattern is
  434.  * sitting in the external variable "re_pat". If found, dot is updated, the
  435.  * window system is notified of the change, and TRUE is returned. If the
  436.  * string isn't found, FALSE is returned.
  437.  */
  438. static
  439. re_backsrch()
  440. {
  441.  
  442.     register struct line *clp;
  443.     register int    tbo;
  444.     int             ntries;
  445.     int             i;
  446.  
  447.     clp = curwp->w_dotp;
  448.     tbo = curwp->w_doto;
  449.  
  450.     /* Start search one position to the left of dot */
  451.     tbo = tbo - 1;
  452.     if (tbo < 0) {
  453.         /* must move up one line */
  454.         clp = lback(clp);
  455.         tbo = llength(clp);
  456.     }
  457.     /*
  458.      * Note this loop does not process the last line, but this editor
  459.      * always makes the last line empty so this is good.
  460.      */
  461.  
  462.     while (clp != (curbp->b_linep)) {
  463.  
  464.         ntries = tbo;
  465.         i = re_search(&re_buff, ltext(clp), llength(clp), tbo, -ntries, ®s);
  466.  
  467.         if (i == -1) {
  468.             clp = lback(clp);
  469.             tbo = llength(clp);
  470.         } else {
  471.             curwp->w_doto = regs.start[0];
  472.             curwp->w_dotp = clp;
  473.             curwp->w_flag |= WFMOVE;
  474.             return (TRUE);
  475.         }
  476.  
  477.     }
  478.  
  479.     return (FALSE);
  480.  
  481. }
  482.  
  483.  
  484. /*
  485.  * Read a pattern. Stash it in the external variable "re_pat". The "pat" is
  486.  * not updated if the user types in an empty line. If the user typed an empty
  487.  * line, and there is no old pattern, it is an error. Display the old
  488.  * pattern, in the style of Jeff Lomicka. There is some do-it-yourself
  489.  * control expansion.
  490.  */
  491. static
  492. re_readpattern(prompt)
  493.     char           *prompt;
  494. {
  495.     register int    s;
  496.     char            tpat[NPAT];
  497.     char           *message;
  498.  
  499.     if (re_pat[0] == '\0')
  500.         s = ereply("%s: ", tpat, NPAT, prompt);
  501.     else
  502.         s = ereply("%s: (default %s) ", tpat, NPAT, prompt, re_pat);
  503.  
  504.     if (s == TRUE) {
  505.         /* New pattern given */
  506.         (VOID) strcpy(re_pat, tpat);
  507.         re_buff.allocated = 40;
  508.         re_buff.buffer = (char *) malloc(re_buff.allocated);
  509.         re_buff.fastmap = fastmap;
  510.         if (casefoldsearch)
  511.             re_buff.translate = upcase;
  512.         else
  513.             re_buff.translate = '\0';
  514.         message = re_compile_pattern(re_pat, strlen(re_pat), &re_buff);
  515.         if (message != '\0') {
  516.             ewprintf("Regex Error: %s", message);
  517.             re_pat[0] = '\0';
  518.             return (FALSE);
  519.         }
  520.         re_compile_fastmap(&re_buff);
  521.     } else if (s == FALSE && re_pat[0] != '\0')
  522.         /* Just using old pattern */
  523.         s = TRUE;
  524.     return (s);
  525. }
  526.  
  527.  
  528.  
  529. /*
  530.  * Cause case to not matter in searches.  This is the default.    If called
  531.  * with argument cause case to matter.
  532.  */
  533. setcasefold(f, n)
  534. {
  535.  
  536.     if (f & FFARG) {
  537.         casefoldsearch = FALSE;
  538.         ewprintf("Case-fold-search unset");
  539.     } else {
  540.         casefoldsearch = TRUE;
  541.         ewprintf("Case-fold-search set");
  542.     }
  543.  
  544.     /*
  545.      * Invalidate the regular expression pattern since I'm too lazy to
  546.      * recompile it.
  547.      */
  548.  
  549.     re_pat[0] = '\0';
  550.  
  551.     return (TRUE);
  552.  
  553. }                /* end setcasefold */
  554.  
  555.  
  556. /*
  557.  * Delete all lines after dot that contain a string matching regex
  558.  */
  559. delmatchlines(f, n)
  560. {
  561.     int             s;
  562.  
  563.     if ((s = re_readpattern("Flush lines (containing match for regexp)")) != TRUE)
  564.         return (s);
  565.  
  566.     s = killmatches(TRUE);
  567.  
  568.     return (s);
  569. }
  570.  
  571.  
  572.  
  573. /*
  574.  * Delete all lines after dot that don't contain a string matching regex
  575.  */
  576. delnonmatchlines(f, n)
  577. {
  578.     int             s;
  579.  
  580.  
  581.     if ((s = re_readpattern("Keep lines (containing match for regexp)")) != TRUE)
  582.         return (s);
  583.  
  584.     s = killmatches(FALSE);
  585.  
  586.     return (s);
  587. }
  588.  
  589.  
  590.  
  591. /* This function does the work of deleting matching lines */
  592. static
  593. killmatches(cond)
  594.     int             cond;
  595. {
  596.     int             s, i;
  597.     int             count = 0;
  598.     struct line    *clp;
  599.  
  600.     clp = curwp->w_dotp;
  601.     if (curwp->w_doto == llength(clp))
  602.         /* Consider dot on next line */
  603.         clp = lforw(clp);
  604.  
  605.     while (clp != (curbp->b_linep)) {
  606.  
  607.         /* see if line matches */
  608.         i = re_search(&re_buff, ltext(clp), llength(clp), 0, llength(clp),
  609.                   ®s);
  610.         /* Delete line when appropriate */
  611.         if ((cond == FALSE && i == -1) || (cond == TRUE && i != -1)) {
  612.             curwp->w_doto = 0;
  613.             curwp->w_dotp = clp;
  614.             count++;
  615.             s = fdelete(llength(clp) + 1, FALSE);
  616.             clp = curwp->w_dotp;
  617.             curwp->w_flag |= WFMOVE;
  618.             if (s == FALSE)
  619.                 return (FALSE);
  620.         } else
  621.             clp = lforw(clp);
  622.     }
  623.  
  624.     ewprintf("%d line(s) deleted", count);
  625.     if (count > 0)
  626.         curwp->w_flag |= WFMOVE;
  627.  
  628.     return (TRUE);
  629. }
  630.  
  631.  
  632. /*
  633.  * Count lines matching regex
  634.  */
  635. cntmatchlines(f, n)
  636. {
  637.     int             s;
  638.  
  639.     if ((s = re_readpattern("Count lines (matching regexp)")) != TRUE)
  640.         return (s);
  641.  
  642.     s = countmatches(TRUE);
  643.  
  644.     return (s);
  645. }
  646.  
  647.  
  648.  
  649. /*
  650.  * Count lines that fail to match regex
  651.  */
  652. cntnonmatchlines(f, n)
  653. {
  654.     int             s;
  655.  
  656.  
  657.     if ((s = re_readpattern("Count lines (not matching regexp)")) != TRUE)
  658.         return (s);
  659.  
  660.     s = countmatches(FALSE);
  661.  
  662.     return (s);
  663. }
  664.  
  665.  
  666.  
  667. /* This function does the work of counting matching lines */
  668. static
  669. countmatches(cond)
  670.     int             cond;
  671. {
  672.     int             i;
  673.     int             count = 0;
  674.     struct line    *clp;
  675.  
  676.     clp = curwp->w_dotp;
  677.     if (curwp->w_doto == llength(clp))
  678.         /* Consider dot on next line */
  679.         clp = lforw(clp);
  680.  
  681.     while (clp != (curbp->b_linep)) {
  682.  
  683.         /* see if line matches */
  684.         i = re_search(&re_buff, ltext(clp), llength(clp), 0, llength(clp),
  685.                   ®s);
  686.         /* Count line when appropriate */
  687.         if ((cond == FALSE && i == -1) || (cond == TRUE && i != -1))
  688.             count++;
  689.         clp = lforw(clp);
  690.     }
  691.  
  692.     if (cond)
  693.         ewprintf("Number of lines matching: %d", count);
  694.     else
  695.         ewprintf("Number of lines not matching: %d", count);
  696.  
  697.     return (TRUE);
  698. }
  699. #else
  700. #include "nullfile.h"
  701. #endif
  702.