home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / vol_300 / 319_01 / def.c < prev    next >
C/C++ Source or Header  |  1990-06-18  |  29KB  |  1,250 lines

  1. /*
  2.     CPP V5 -- definition and expansion routines.
  3.  
  4.     source:  def.c
  5.     started: October 22, 1985
  6.     version:
  7.         July 21, 1988
  8.         February 14, 1989:
  9.             bug fixes in pp_def().
  10.             bug fix in substitute().
  11.         August 2, 1989:
  12.             support for C++ single-line comment added to pp_def().
  13.             overflow checks made in substitute().
  14.             Big buffers allocated on heap in expand(), rescan().
  15.  
  16.     Written by Edward K. Ream.
  17.     This software is in the public domain.
  18.  
  19.     See the read.me file for disclaimer and other information.
  20. */
  21.  
  22. #include "cpp.h"
  23.  
  24. /*
  25.     Forward declarations of internal routines.
  26. */
  27.  
  28. static void    aexpand        (char *name, int *nargs, char **p1, char *out,
  29.                     char **p2);
  30. static void    aparse        (char *name, int *nargs, char *out, char **p);
  31. static bool    a1parse        (char *name, char *out, int max_out);
  32. static void    disable_name    (char *name);
  33. static void    enable_name    (void);
  34. static bool    is_disabled    (char *name);
  35. static void    expand        (char *name, int nargs, char *rtext, char *out,
  36.                     int max_out);
  37. static void    rescan        (char *name, char *in, char *out, int max_out,
  38.                     bool bounded_flag);
  39. static void    substitute    (char *name, int nargs, char *in, char **args,
  40.                     char *out, int maxout);
  41.  
  42. /*
  43.     Define static variables used only by this module.
  44. */
  45. static char *    x_stack [MAX_MDEPTH];    /* Pointers to disabled macro names. */
  46. static int    x_count = 0;        /* Number of disabled macro names.   */
  47.  
  48. /*
  49.     Externally visible routines.
  50. */
  51.  
  52. /*
  53.     Check an identifier seen at the outer level to see whether it
  54.     could be a macro call and expand it and return TRUE if so.
  55. */
  56. bool
  57. outer_expand(name, old_mflag)
  58. char    *name;
  59. bool    old_mflag;
  60. {
  61.     char     *p, *p1, *p2;
  62.     int    i;
  63.     char *    rtext;            /* Replacement text for name.       */
  64.     int    nargs;            /* Required number of args.       */
  65.     char    temp_buf[MAX_RTEXT];    /* Temp spot for macro.           */    
  66.  
  67.     TRACEPB("outer_expand", printf("(%s)\n", name));
  68.  
  69.     if (m_flag || old_mflag || !mst_lookup(name, &rtext, &nargs)) {
  70.         RETURN_BOOL("outer_expand", FALSE);
  71.     }
  72.     else if (nargs != -1) {
  73.         skip_ws(TRUE);
  74.         if (ch != '(') {
  75.             warn3("Function-like macro, ", name,
  76.                 "appears without arguments.");
  77.             RETURN_BOOL("outer_expand", FALSE);
  78.         }
  79.     }
  80.  
  81.     /*
  82.         Expand the macro into temp_buf[].
  83.         This will use ONLY file characters.
  84.     */
  85.     expand(name, nargs, rtext, &temp_buf[0], MAX_RTEXT);
  86.  
  87.     /*
  88.         Expand will have changed ch to point past any args.
  89.         Use sys_fpb so that
  90.         1.  The old ch will appear just AFTER the macro expansion.
  91.         2.  It will not be mistaken added to the rescan buffer.
  92.     */
  93.     sys_fpb(ch);
  94.  
  95.     /*
  96.         Fully rescan temp_buf[] into macro_buf[].
  97.         Use an unbounded rescan, i.e., allow file characters to
  98.         complete a partial list of actual parameters which may
  99.         be started in the rescanned text.
  100.     */
  101.     p_rescan = NULL;
  102.     m_flag   = FALSE;
  103.     rescan(name, &temp_buf[0], ¯o_buf[0], MAX_RTEXT, FALSE);
  104.  
  105.     /* Make sure we save the now current ch. */
  106.     sys_fpb(ch);
  107.  
  108.     /* Delete any internal flags from the output. */
  109.     TRACEP("outer_expand", printf("macro1: %s\n", pr_str(¯o_buf[0])));
  110.  
  111.     p1 = p2 = ¯o_buf[0];
  112.     while (*p2) {
  113.         if (*p2 == EXPAND_OFF || *p2 == EORT) {
  114.             p2++;
  115.         }
  116.         else {
  117.             *p1++ = *p2++;
  118.         }
  119.     }
  120.     *p1 = '\0';
  121.     TRACEP("outer_expand", printf("macro2: %s\n", pr_str(¯o_buf[0])));
  122.  
  123.     /*
  124.         Inhibit any further macro expansion until the end of the
  125.         current local input stream.
  126.     */
  127.  
  128.     if (m_flag) {
  129.         warning("outer_expand: Can't happen.");
  130.     }
  131.     m_flag = TRUE;
  132.     p_rescan = ¯o_buf[0];
  133.  
  134.     /* Reload ch from the global macro buffer. */
  135.     sysnext();
  136.  
  137.     RETURN_BOOL("outer_expand", TRUE);
  138. }
  139.  
  140. /*
  141.     Handle the #define directive by parsing the statement, and entering
  142.     the macro's name, number of arguments and replacement text into the
  143.     macro table.
  144.  
  145.     Formal arguments are found and replaced by a flag byte followed by the
  146.     number of the formal argument.
  147. */
  148. void
  149. pp_def()
  150. {
  151.     int    i;
  152.  
  153.     char    name[MAX_SYMBOL];    /* Name of macro.        */
  154.  
  155.     char    rtext [MAX_RTEXT];    /* Replacement text.        */
  156.     int    rcount = 0;        /* Number of chars in rtext[].    */
  157.  
  158.     char    atext [MAX_ATEXT];    /* Text of formal parameters.    */
  159.     int    acount = 0;        /* Number of chars in atext[].    */
  160.  
  161.     char *    argps [MAX_NARGS];    /* Pointers into atext[].     */
  162.     int    nargs = 0;        /* Number of entries in argps[]. */
  163.  
  164.     TICKB("pp_def");
  165.  
  166.     /* Make sure the name is present. */
  167.     if (!isid1(ch)) {
  168.         error("#define ignored--no symbol name given.");
  169.         skip_1line();
  170.         return;
  171.     }
  172.  
  173.     /* Get the macro name into name. */
  174.     t_id(&name[0], MAX_SYMBOL);
  175.     TRACEPN("pp_def", printf("macro name is %s\n", name));
  176.     
  177.     if (ch != '(') {
  178.         /* Indicate no argument list and continue. */
  179.         nargs = -1;
  180.         goto gettext;
  181.     }
  182.     else {
  183.         sysnext();
  184.     }
  185.  
  186.     if (ch == ')') {
  187.         sysnext();
  188.         nargs = 0;
  189.         goto gettext;
  190.     }
  191.  
  192.     /* 
  193.         Put the formal arguments into dynamic storage.
  194.         Set pointers to the arguments argps[].
  195.     */
  196.     while (nargs < MAX_NARGS) {
  197.  
  198.         skip_ws(TRUE);
  199.  
  200.         if (!isid1(ch)) {
  201.             err2(    "#define ignored--",
  202.                 "formal arg must be an identifier.");
  203.             skip_1line();
  204.             RETURN_VOID("pp_def");
  205.         }
  206.  
  207.         /* Copy one formal arg to atext[]. */
  208.         t_id(&t_symbol[0], MAX_SYMBOL);
  209.         argps [nargs++] = &atext[acount];
  210.         str_cpy(atext + acount, t_symbol);
  211.         acount += strlen(t_symbol)+1;
  212.  
  213.         TRACEPN("pp_def", printf("formal [%d] is %s\n",
  214.             nargs-1, pr_str(argps [nargs-1])));
  215.  
  216.         /* Bug fix: 2/14/89 */
  217.         skip_ws(TRUE);
  218.             
  219.         if (ch == ')') {
  220.             sysnext();
  221.             goto gettext;
  222.         }
  223.         else if (ch == ',') {
  224.             sysnext();
  225.         }
  226.     }
  227.     error("#define ignored--too many arguments.");
  228.     skip_1line();
  229.     RETURN_VOID("pp_def");
  230.  
  231.  
  232.     /*
  233.         At this point, nargs contains the number of formal arguments,
  234.         or -1 if no argument list was given.
  235.         0 is allowed and means that the argument list was ().
  236.     */
  237.  
  238. gettext:
  239.  
  240.     TRACEPN("pp_def", printf("nargs is %d\n", nargs));
  241.  
  242.     skip_bl();
  243.  
  244.     /*
  245.         Put the replacement text into rtext[].
  246.         The text is tokenized, which means that partial comments
  247.         or strings are not valid.
  248.  
  249.         Replace formal arg n by n FLAG on the fly.
  250.     */
  251.  
  252.     for (rcount = 0;;) {
  253.         switch (ch) {
  254.  
  255.         case END_FILE:
  256.         case '\n':
  257.             goto done;
  258.  
  259.         /* Replace a sequence of white space by a blank. */
  260.         case ' ':
  261.         case '\t':
  262.             sysnext();
  263.             if (rcount > 0 && rtext[rcount-1] != ' ') {
  264.                 rtext[rcount++] = ' ';
  265.             }
  266.             continue;
  267.  
  268.         /* Look for continuation of definitions. */
  269.         case '\\':
  270.             sysnext();
  271.             if (ch == '\n') {
  272.                 sysnlput();
  273.                 sysnext();
  274.                 if (rcount > 0 && rtext[rcount-1] != ' ') {
  275.                     rtext[rcount++] = ' ';
  276.                 }
  277.                 bump_line();
  278.             }
  279.             else {
  280.                 warning("Backslash not followed by newline.");
  281.                 rtext[rcount++] = '\\';
  282.             }
  283.             continue;
  284.  
  285.         /* Eliminate comments. */
  286.         case '/':
  287.             sysnext();
  288.             if (ch == '*') {
  289.                 if (rcount > 0 && rtext[rcount-1] != ' ') {
  290.                     rtext[rcount++] = ' ';
  291.                 }
  292.                 sysnext();
  293.                 skip_comment();
  294.             }
  295.             else if (slc_flag && ch == '/') {
  296.                 /* 8/1/89 C++ style single-line comment. */
  297.                 if (rcount > 0 && rtext[rcount-1] != ' ') {
  298.                     rtext[rcount++] = ' ';
  299.                 }
  300.                 while (ch != END_FILE && ch != '\n') {
  301.                     sysnext();
  302.                 }
  303.                 goto done;
  304.             }
  305.             else {
  306.                 rtext[rcount++] = '/';
  307.             }
  308.             continue;
  309.  
  310.         /* Handle complete strings. */
  311.         case '"':
  312.         case '\'':
  313.             t_string(&rtext[rcount], MAX_RTEXT-rcount, TRUE);
  314.             rcount += t_length;
  315.             continue;
  316.  
  317.         /* Handle # formal param and ## */
  318.         case '#':
  319.             sysnext();
  320.             if (ch == '#') {
  321.                 sysnext();
  322.                 rtext[rcount++] = CONCAT_FLAG;
  323.                 continue;
  324.             }
  325.  
  326.             /* We expect an identifier here. */
  327.  
  328.             /* Bug fix 2/14/89:  delete leading white space here. */
  329.             if (ch == ' ' || ch == '\t') {
  330.                 if (rcount > 0) {
  331.                     rtext[rcount++] = ' ';
  332.                 }
  333.                 skip_bl();
  334.             }
  335.             if (isid1(ch)) {
  336.                 t_id(&rtext[rcount], MAX_RTEXT-rcount);
  337.                 i = arg_search( rtext + rcount, nargs,
  338.                         &argps[0]);
  339.                 if (i >= 0) {
  340.                     /* Replace id with flags. */
  341.                     rtext[rcount++] = POUND_FLAG;
  342.                     rtext[rcount++] = i + ARG_OFFSET;
  343.                 }
  344.                 else {
  345.                     /* Accept id. */
  346.                     rcount += t_length;
  347.                 }
  348.             }
  349.             else {
  350.                 /* Replace the blank with # */
  351.                 rtext[rcount++] = '#';
  352.                 warning("Id expected after '#'.");
  353.             }
  354.             continue;
  355.  
  356.         default:
  357.  
  358.             /* Possible formal parameter. */
  359.             if (isid1(ch)) {
  360.                 t_id(&rtext[rcount], MAX_RTEXT-rcount);
  361.                 i = arg_search(    rtext + rcount, nargs,
  362.                         &argps[0]);
  363.                 if (i >= 0) {
  364.                     rtext[rcount++] = ARG_FLAG;
  365.                     rtext[rcount++] = i+ARG_OFFSET;
  366.                 }
  367.                 else {
  368.                     rcount += t_length;
  369.                 }
  370.             }
  371.             else {
  372.                 rtext[rcount++] = ch;
  373.                 sysnext();
  374.             }
  375.             continue;
  376.         }
  377.     }
  378.  
  379. done:
  380.  
  381.     /* This check is made only here to save a little time. */
  382.     if (rcount >= MAX_RTEXT) {
  383.         fatal("Replacement text of macro is too long.");
  384.     }
  385.  
  386.     /* Strip at most one blank off the end of the definition. */
  387.     if (rtext[rcount - 1] == ' ') {
  388.         rcount--;
  389.     }
  390.  
  391.     /* Terminate the replacement text properly. */
  392.     rtext[rcount] = '\0';
  393.  
  394.     TRACEPN("pp_def", printf("rtext: <%s>\n", pr_str(rtext)));
  395.  
  396.     /*
  397.         Enter the symbol, replacement text and number of arguments
  398.         into the macro table.
  399.  
  400.         Note that mst_enter allocates space for copies of name and
  401.         rtext so the fact that they are on the stack doesn't matter.
  402.     */
  403.     mst_enter(&name[0], rtext, nargs);
  404.     skip_pp();
  405.  
  406.     RETURN_VOID("pp_def");
  407. }
  408.  
  409.  
  410. /*
  411.     Internal Routines.
  412. */
  413.  
  414. /*
  415.     Macro expand all arguments in old_argps and leave them in new_atext[].
  416.     Put pointers to them in new_argps[].
  417. */
  418. static void
  419. aexpand(name, nargs, old_argps, new_atext, new_argps)
  420. char *    name;        /* Name of macro whose args are being expanded.    */
  421. int *    nargs;        /* Number of args the macro should have.    */
  422. char *    old_argps[];    /* Pointers to unexpanded tokens for each arg.    */
  423. char     new_atext[];    /* Storage for expanded args.            */
  424. char *    new_argps[];    /* Pointers to expanded args.            */
  425. {
  426.     char    save_ch;
  427.     int    i;
  428.     char *    new;
  429.     int    new_count = 0;
  430.  
  431.     TRACEPB("aexpand", printf("(%s, %d, %p, %p, %p)\n",
  432.         name, *nargs, old_argps, new_atext, new_argps));
  433.  
  434.     /*
  435.         Save the current value of ch!
  436.         rescan will change ch, and it must be restored afterwards.
  437.     */
  438.     save_ch = ch;
  439.  
  440.     /* Rescan all arguments. */
  441.     for (i = 0; i < *nargs; i++) {
  442.         new = &new_atext[new_count];
  443.         new_argps[i] = new;
  444.  
  445.         /* Do a bounded rescan. */
  446.         rescan(    "<NO_NAME>", old_argps[i], new,
  447.             MAX_RTEXT-new_count, TRUE);
  448.  
  449.         new_count += strlen(new)+1;
  450.     }
  451.  
  452.     /* Restore ch */
  453.     ch = save_ch;
  454.  
  455.     RETURN_VOID("aexpand");
  456. }
  457.  
  458. /*
  459.     Parse a list of actual arguments into atext[] and put pointers to
  460.     the arguments in argps[].  
  461.     Set *nargs to -1 on error.
  462.     The size of atext[] is MAX_ATEXT and the size of argps is MAX_ARGPS.
  463. */
  464. static int call_start;        /* Line where arguments started.    */
  465.  
  466. static void
  467. aparse(name, nargs, atext, argps)
  468. char *    name;        /* Macro whose arguments are being parsed.       */
  469. int *    nargs;        /* Pointer to arg count.               */
  470. char     atext[];    /* Buffer into which UNEXPANDED arguments are put. */
  471. char *    argps[];    /* Array of pointers to arguments.           */
  472. {
  473.     int    arg_c = 0;    /* Number of arguments parsed so far.    */
  474.     int    length = 0;    /* Total length of arguemnts parsed.    */
  475.     char    msg [100];
  476.     char    line [10];
  477.     bool    flag;
  478.  
  479.     if (*nargs == -1) {
  480.         return;
  481.     }
  482.  
  483.     TRACEPB("aparse", printf("(%s, %d, %p, %p)\n",
  484.         name, *nargs, atext, argps));
  485.  
  486.     if (ch != '(') {
  487.         syserr("aparse: Can't happen.");
  488.     }
  489.     else {
  490.         sysnext();
  491.     }
  492.  
  493.     /* Save starting line number of the macro call. */
  494.     call_start = t_line;
  495.  
  496.     if (ch == ')') {
  497.         sysnext();
  498.         goto check;
  499.     }
  500.  
  501.     for(;;) {
  502.         /* Set pointer to the start of the argument. */
  503.         argps[arg_c] = &atext[length];
  504.  
  505.         /* Parse the argument. */
  506.         flag = a1parse(name, &atext[length], MAX_ATEXT-length);
  507.         if (flag || ch == END_FILE) {
  508.             *nargs = -1;
  509.             RETURN_VOID("aparse");
  510.         }
  511.         else {
  512.             length += strlen(argps[arg_c])+1;
  513.             arg_c++;
  514.         }
  515.  
  516.         if (ch == ')') {
  517.             sysnext();
  518.             break;
  519.         }
  520.         else if (ch == ',') {
  521.             sysnext();
  522.         }
  523.         else {
  524.             /* Error detected in a1parse. */
  525.             break;
  526.         }
  527.     }
  528.  
  529. check:
  530.     if (arg_c != *nargs) {
  531.         if (call_start != t_line) {
  532.             strcpy(msg, "Call to macro ");
  533.             strcat(msg, name);
  534.             strcat(msg, " starting at line ");
  535.             conv2s(call_start, line);
  536.             strcat(msg, line);
  537.         }
  538.         else {
  539.             strcpy(msg, "Call to macro ");
  540.             strcat(msg, name);
  541.         }
  542.         strcat(msg, " requires ");
  543.         conv2s(*nargs, line);
  544.         strcat(msg, line);
  545.         strcat(msg, " arguments.");
  546.         error(msg);
  547.         *nargs = -1;
  548.     }
  549.     TRACEP("aparse",
  550.         {int i;
  551.         if (*nargs != 0) printf("\n");
  552.         for(i = 0; i < *nargs; i++) {
  553.             printf("arg[%d]: @%p=%p: <%s>\n",
  554.                 i, &argps[i], argps[i], pr_str(argps[i]));
  555.         }});
  556.     RETURN_VOID("aparse");
  557. }
  558.  
  559. /*
  560.     Parse one actual argument into out[].
  561.     The size of out is max_length.
  562.     Return TRUE if an error was seen.
  563.  
  564.     Formal arguments are simply identifiers, which are handled by t_id(),
  565.     but actual arguments are LISTS of tokens separated by commas.  As an
  566.     added twist, commas inside single or double quotes, or commas which are
  567.     "protected" by additional parentheses do NOT separate actual args.
  568.     Thus, each of the following calls have to M have ONE actual argument:
  569.  
  570.         M(a)
  571.         M(a * N(c,b))
  572.         M(',')
  573.         M("a,b")
  574.         M((a,b))
  575.         M((a,")",b))
  576.  
  577.     This routine changes all comments and white space to a single blank.
  578. */
  579. static bool
  580. a1parse(name, out, max_out)
  581. char *    name;            /* Name of the macro being expanded.    */
  582. char *    out;            /* Output buffer.            */
  583. int    max_out;        /* Size of out[].            */
  584. {
  585.      int count;    /* Number of characters in out[].    */
  586.     int plevel;    /* Parenthesis level.            */
  587.  
  588.     char buffer [100];    /* Buffer for messages.        */
  589.     char linebuf [10];    /* Buffer for messages.        */
  590.  
  591.     TRACEPB("a1parse", printf("(%s, %p, %d)\n",
  592.         name, out, max_out));
  593.  
  594.     /* No parens have been seen yet. */
  595.     plevel = 0;
  596.  
  597.     for(count = 0;;) {
  598.  
  599.         /* Make sure there is room for one more. */
  600.         if (count >= max_out) {
  601.             goto toolong;
  602.         }
  603.  
  604.         switch (ch) {
  605.  
  606.         case END_FILE:
  607.             goto runon;
  608.  
  609.         case '\n':
  610.             sysnlput();
  611.             sysnext();
  612.             bump_line();
  613.             if (count > 0 && out[count-1] != ' ') {
  614.                 out[count++] = ' ';
  615.             }
  616.             continue;
  617.  
  618.         /* Convert a sequence of white space to one blank. */
  619.         case ' ':
  620.         case '\t':
  621.             sysnext();
  622.             if (count > 0 && out[count-1] != ' ') {
  623.                 out[count++] = ' ';
  624.             }
  625.             continue;
  626.  
  627.         case '\\':
  628.             sysnext();
  629.             if (ch == '\n') {
  630.                 sysnlput();
  631.                 sysnext();
  632.                 if (count > 0 && out[count-1] != ' ') {
  633.                     out[count++] = ' ';
  634.                 }
  635.                 bump_line();
  636.             }
  637.             else {
  638.                 out[count++] = '\\';
  639.             }
  640.             continue;
  641.  
  642.         case ',':
  643.             if (plevel == 0) {
  644.                 goto end_arg;
  645.             }
  646.             else {
  647.                 out[count++] = ch;
  648.                 sysnext();
  649.             }
  650.             continue;
  651.                 
  652.         case ')':
  653.  
  654.             if (plevel == 0) {
  655.                 goto end_arg;
  656.             }
  657.             else {
  658.                 plevel--;
  659.                 out[count++] = ch;
  660.                 sysnext();
  661.                 continue;
  662.             }
  663.  
  664.         case '(':
  665.             plevel++;
  666.             out[count++] = ch;
  667.             sysnext();
  668.             continue;
  669.  
  670.         case '"':
  671.         case '\'':
  672.             t_string(&out[count], max_out-count, TRUE);
  673.             count += t_length;
  674.             if (count >= max_out) {
  675.                 goto toolong;
  676.             }
  677.             continue;
  678.  
  679.         case '/':
  680.             sysnext();
  681.             if (ch == '*') {
  682.                 sysnext();
  683.                 skip_comment();
  684.  
  685.                 /* Change a comment into one blank. */
  686.                 if (count > 0 && out[count] != ' ') {
  687.                     out[count++] = ' ';
  688.                 }
  689.             }
  690.             else {
  691.                 out[count++] = '/';
  692.             }
  693.             continue;
  694.  
  695.         default:
  696.             if (isid1(ch)) {
  697.                 t_id(&out[count], max_out-count);
  698.                 count += t_length;
  699.                 if (count >= max_out) {
  700.                     goto toolong;
  701.                 }
  702.             }
  703.             else {
  704.                 out[count++] = ch;
  705.                 sysnext();
  706.             }
  707.         }
  708.     }
  709.  
  710. end_arg:
  711.     /* Finish off the argument. */
  712.     out[count] = '\0';
  713.     TRACEP("a1parse", printf("out: <%s>\n", pr_str(out)));
  714.     RETURN_BOOL("a1parse", FALSE);
  715.  
  716. runon:
  717.     if (call_start != t_line) {
  718.         conv2s(call_start, linebuf);
  719.         str_cpy(buffer, "Runon macro call at line ");
  720.         str_cat(buffer, linebuf);
  721.     }
  722.     else {
  723.         str_cpy(buffer, "Runon macro call");
  724.     }
  725.     str_cat(buffer, "--last arg set to null.");
  726.     error(buffer);
  727.     RETURN_BOOL("a1parse", TRUE);
  728.  
  729. toolong:
  730.     conv2s(call_start, linebuf);
  731.     str_cpy(buffer, "Macro arg starting at line ");
  732.     str_cat(buffer, linebuf);
  733.     str_cat(buffer, " is too long.");
  734.     fatal(buffer);
  735.     RETURN_BOOL("a1parse", TRUE);
  736. }
  737.  
  738. /*
  739.     Search an array of formal arguments for a match.
  740. */
  741. static int
  742. arg_search(name, nargs, argps)
  743. char *    name;    /* Name to search for.    */
  744. int    nargs;    /* Number of args.    */
  745. char **    argps;    /* Array of args.    */
  746. {
  747.     int i;
  748.  
  749.     /* See if the id is a formal arg. */
  750.     for (i = 0; i < nargs ; i++) {
  751.         if (str_eq(name, argps[i])) {
  752.             return i;
  753.         }
  754.     }
  755.     return -1;
  756. }
  757.  
  758. /*
  759.     Disable the expansion of one name.
  760. */
  761. static void
  762. disable_name(name)
  763. char *name;
  764. {
  765.     TRACEP("disable_name", printf("(%s)\n", name));
  766.  
  767.     if (x_count < MAX_MDEPTH) {
  768.         x_stack [x_count++] = name;
  769.     }
  770. }
  771.  
  772. /*
  773.     Enable the expansion of the last disabled name.
  774. */
  775. static void
  776. enable_name()
  777. {
  778.     TICK("enable_name");
  779.  
  780.     if (x_count == 0) {
  781.         syserr("enable_name: Can't happen.");
  782.     }
  783.     x_count--;
  784. }
  785.  
  786. /*
  787.     Fully expand the macro described by name, nargs, rtext.
  788. */
  789. static void
  790. expand(name, nargs, rtext, out, max_out)
  791. char *    name;        /* Name of macro expanded.    */
  792. int    nargs;        /* Required number of args.    */
  793. char *    rtext;        /* Replacement text for name.    */
  794. char *    out;        /* Output buffer.        */
  795. int    max_out;    /* Size of out[].        */
  796. {
  797.     char *  old_argps[MAX_NARGS];
  798.     char *  new_argps[MAX_NARGS];
  799.  
  800.     char *    old_atext;    /* Buffer for actual arguments.       */
  801.     char *    new_atext;    /* Buffer for expansion of args.   */    
  802.  
  803.     TRACEPB("expand", printf("(%s, %d, <%s>, %p, %d) ch: %s\n",
  804.         name, nargs, pr_str(rtext), out, max_out, pr_ch(ch)));
  805.  
  806.     /*
  807.         Allocate memory on the heap so we don't crash the stack.
  808.     */
  809.     old_atext = m_alloc(MAX_ATEXT);
  810.     new_atext = m_alloc(MAX_ATEXT);
  811.  
  812.     /* Parse all arguments to the macro. */
  813.     aparse(name, &nargs, old_atext, old_argps);
  814.  
  815.     /* Fully macro expand all arguments. */
  816.     aexpand(name, &nargs, &old_argps[0], &new_atext[0], &new_argps[0]);
  817.  
  818.     /* Substitute all expanded arguments into rtext[], giving out[]. */
  819.     substitute(name, nargs, rtext, &new_argps[0], &out[0], max_out);
  820.  
  821.     /* Free all locally allocated memory. */
  822.     m_free(old_atext);
  823.     m_free(new_atext);
  824.  
  825.     RETURN_VOID("expand");
  826. }
  827.  
  828. /*
  829.     Return TRUE if macro expansion of name has been disabled.
  830. */
  831. static bool
  832. is_disabled(name)
  833. char *name;
  834. {
  835.     int i;
  836.  
  837.     TRACEPB("is_disabled", printf("(%s), x_count: %d\n",
  838.         name, x_count));
  839.  
  840.     for (i = x_count-1; i >= 0; i--) {
  841.         TRACEP("is_disabled_v", printf("compare: <%s> <%s>\n",
  842.             name, x_stack[i]));
  843.         if (str_eq(name, x_stack[i])) {
  844.             RETURN_BOOL("is_disabled", TRUE);
  845.         }
  846.     }
  847.     RETURN_BOOL("is_disabled",FALSE);
  848. }
  849.  
  850. /*
  851.     This is it: the guts of macro expansion.
  852.     Fully rescan in[] to out[], looking for macro calls.
  853.  
  854.     A key distinction:  bounded versus unbounded rescanning.
  855.     Set the global b_rescan to TRUE for bounded rescans.  
  856.     sysnext() returns EORT when p_rescan is empty on bounded rescans.
  857.  
  858.     The CALLER of rescan must save and restore ch as appropriate.
  859.     (Note that rescan is called recursively from within, so it must
  860.     save ch in that case just as any other caller must.
  861.  
  862.     Note that rescan will completely process rescan buffer in all cases.
  863.     For unbounded rescans, the previous rescan buffer is appended to in[].
  864.     For bounded rescans, the previous rescan buffer is kept separate.
  865. */
  866. static void
  867. rescan(name, in, out, max_out, bounded_flag)
  868. char *    name;        /* Name of macro being rescanned.    */
  869. char *    in;        /* Text to be rescanned.        */
  870. char *    out;        /* Output buffer.            */
  871. int    max_out;    /* Size of out[].            */
  872. bool    bounded_flag;    /* TRUE if bounded rescan.        */
  873. {
  874.     int    save_mflag;        /* Saved m_flag.    */
  875.     char *    save_prescan;        /* Saved p_rescan.    */
  876.  
  877.     char *    local_in;    /* Local rescan buffer.    */
  878.     char *    expand_buf;    /* Expansion buffer.    */
  879.     char *    id_buf;        /* Saved rescan id.    */
  880.  
  881.     int    out_len = 0;        /* Index into out[].    */
  882.     char *    rtext;            /* Ptr to replacement text.    */
  883.     int    nargs;            /* Required number of args.    */
  884.     int    i, level;
  885.  
  886.     TRACEPB("rescan", printf("(%s, <%s>, %p, %d, %s) ch: %s\n",
  887.         name, pr_str(in), out, max_out, pr_bool(bounded_flag),
  888.         pr_ch(ch)));
  889.  
  890.     /*
  891.         Allocate buffers here so we don't crash the stack.
  892.     */
  893.     local_in   = m_alloc(MAX_RTEXT);
  894.     expand_buf = m_alloc(MAX_RTEXT);
  895.     id_buf     = m_alloc(MAX_SYMBOL);
  896.  
  897.     if (bounded_flag) {
  898.         /* Save old rescan buffer and use in[] as the new buffer. */
  899.         save_mflag   = m_flag;
  900.         save_prescan = p_rescan;
  901.  
  902.         /*
  903.             Indicate the end of the buffer with EORT flag.
  904.             Copy in[] to local_in[] so we have room for it.
  905.         */
  906.         p_rescan = &local_in[0];
  907.         strcpy(&local_in[0], in);
  908.         i = strlen(&local_in[0]);
  909.         local_in[i++] = EORT;
  910.         local_in[i]   = '\0';
  911.     }
  912.     else {
  913.         /*
  914.             The key programming trick in this whole file.
  915.             Set rescan to old rescan appended behind in[].
  916.         */
  917.         if (!m_flag) {
  918.             p_rescan = in;
  919.         }
  920.         else {
  921.             if (strlen(in) + strlen(p_rescan) >= max_out) {
  922.                 goto run_on;
  923.             }
  924.             str_cpy(local_in, in);
  925.             str_cat(&local_in[0], p_rescan);
  926.             p_rescan = &local_in[0];
  927.         }
  928.     }
  929.  
  930.     /* Get input from rescan buffer. */
  931.     m_flag = TRUE;
  932.  
  933.     TRACEP("rescan",
  934.         printf("p_rescan: strlen: %d, <%s>\n",
  935.             strlen(p_rescan), pr_str(p_rescan)));
  936.  
  937.     /*
  938.         Note1:    At this point, sysnext() will return the characters
  939.         that have just been placed in the rescan buffer pointed to by
  940.         p_rescan.  This means that t_id(), t_string(), etc.  may be
  941.         used to parse the rescan buffer.
  942.  
  943.         Note2:  For bounded rescans, sysnext() will return EORT when
  944.         the rescan buffer is exhausted.  This means that t_string() and
  945.         skip_comment() will complain about "unexpected end of input," 
  946.         which is as it should be.
  947.     */
  948.  
  949.     /* Disable further expansions of name for the duration. */
  950.     disable_name(name);
  951.  
  952.     /*
  953.         Scan the buffer until it is exhausted or a macro call is seen.
  954.         (The recursive call to rescan() will finish off the buffer.)
  955.     */
  956.     out_len = 0;
  957.     sysnext();
  958.     while (m_flag) {
  959.  
  960.         if (out_len >= max_out) {
  961.             goto run_on;
  962.         }
  963.  
  964.         if (ch == EORT) {
  965.             break;
  966.         }
  967.         else if (ch == EXPAND_OFF) {
  968.             /* Inhibited identifier. */
  969.             out[out_len++] = ch;
  970.             sysnext();
  971.             t_id(&out[out_len], max_out-out_len);
  972.             out_len += strlen(&out[out_len]);
  973.         }
  974.         else if (ch == '"' || ch == '\'') {
  975.             /* Handle complete strings. */
  976.             t_string(&out[out_len], max_out-out_len, TRUE);
  977.             out_len += t_length;
  978.         }
  979.         else if (isid1(ch)) {
  980.             /* Handle identifiers. */
  981.             t_id(&id_buf[0], MAX_SYMBOL);
  982.  
  983.             /*
  984.                 Possible inner macro call.
  985.                 Do not adjust out_len until we know for sure.
  986.             */
  987.             if (!mst_lookup(&id_buf[0], &rtext, &nargs)) {
  988.                 /* Not a macro call. */
  989.                 str_cpy(&out[out_len], &id_buf[0]);
  990.                 out_len += t_length;
  991.                 continue;
  992.             }
  993.             else if (is_disabled(&id_buf[0])) {
  994.                 /*
  995.                     Disabled macro.
  996.                     Inhibit ALL further expansion.
  997.                 */
  998.                 out[out_len++] = EXPAND_OFF;
  999.                 str_cpy(&out[out_len], &id_buf[0]);
  1000.                 out_len += t_length;
  1001.                 continue;
  1002.             }
  1003.             else if (nargs > -1) {
  1004.                 skip_ws(TRUE);
  1005.                 /*
  1006.                     Function-like macro name without args
  1007.                     are ok in bounded expansions.
  1008.                 */
  1009.                 if (ch != '(') {
  1010.                     if (ch != EORT && !bounded_flag) {
  1011.                        warn3("Function-like macro ",
  1012.                          &id_buf[0],
  1013.                          " appears without arguments.");
  1014.                     }
  1015.                     str_cpy(&out[out_len], &id_buf[0]);
  1016.                     out_len += t_length;
  1017.                     continue;
  1018.                 }
  1019.             }
  1020.  
  1021.             /* Valid macro call. */
  1022.             if (out_len >= max_out) {
  1023.                 goto run_on;
  1024.             }
  1025.  
  1026.             out[out_len] = '\0';
  1027.  
  1028.             /* Expand the text into expand_buf[]. */
  1029.             expand(    &id_buf[0], nargs, rtext,
  1030.                 &expand_buf[0], MAX_RTEXT);
  1031.  
  1032.             /*
  1033.                 Do not pull an extra file character into
  1034.                 the buffer.
  1035.             */
  1036.             if (m_flag) {
  1037.                 /* Append current ch to expand_buf[]. */
  1038.                 i = strlen(&expand_buf[0]);
  1039.                 expand_buf[i++] = ch;
  1040.                 expand_buf[i] = '\0';
  1041.             }
  1042.             else {
  1043.                 sys_fpb(ch);
  1044.             }
  1045.  
  1046.             /* Rescan will append the rest of p_rescan. */
  1047.             TRACEP("rescan", printf("recursive call. ch: %s\n",
  1048.                 pr_ch(ch)));
  1049.             rescan(    &id_buf[0], &expand_buf[0],
  1050.                 &out[out_len], MAX_RTEXT-out_len, FALSE);
  1051.  
  1052.             out_len=strlen(&out[0]);
  1053.  
  1054.             /* Use the ch that the recursive call left behind. */
  1055.             continue;
  1056.         }
  1057.         /* Comments might appear here due to concatenation. */
  1058.         else if (ch == '/') {
  1059.             sysnext();
  1060.             if (ch == '*') {
  1061.                 sysnext();
  1062.                 skip_comment();
  1063.             }
  1064.             else {
  1065.                 out[out_len++] = '/';
  1066.             }
  1067.         }
  1068.         else {
  1069.             out[out_len++] = ch;
  1070.             sysnext();
  1071.         }
  1072.     }
  1073.  
  1074.     out[out_len] = '\0';
  1075.     TRACEP("rescan", printf("out: <%s>\n", pr_str(out)));
  1076.  
  1077.     enable_name();
  1078.  
  1079.     /* Restore the b_rescan global. */
  1080.     if (bounded_flag) {
  1081.         /* Restore the old rescan buffer. */
  1082.         m_flag   = save_mflag;
  1083.         p_rescan = save_prescan;
  1084.     }
  1085.     else {
  1086.         /* Everything is complete. */
  1087.         m_flag = FALSE;
  1088.     }
  1089.  
  1090.     /* Free buffers. */
  1091.     m_free(local_in);
  1092.     m_free(expand_buf);
  1093.     m_free(id_buf);
  1094.  
  1095.     RETURN_VOID("rescan");
  1096.  
  1097. run_on:
  1098.     strcpy(id_buf, "Macro expansion for ");
  1099.     strcat(id_buf, name);
  1100.     strcat(id_buf, " is too long...\nExpansion set to empty string");
  1101.     error(id_buf);
  1102.  
  1103.     /* Truncate the ENTIRE macro. */
  1104.     out[0] = '\0';
  1105.  
  1106.     /* Free the buffers. */
  1107.     m_free(local_in);
  1108.     m_free(expand_buf);
  1109.     m_free(id_buf);
  1110.  
  1111.     RETURN_VOID("rescan");
  1112. }
  1113.  
  1114. /*
  1115.     Substitute expanded arguments from argps[] into in[] leaving out[].
  1116.     8/1/89: run-on logic added.
  1117. */
  1118. static void
  1119. substitute(name, nargs, rtext, argps, out, max_out)
  1120. char *    name;    /* The name of the macro being expanded.    */
  1121. int    nargs;    /* Number of args for the macro.        */
  1122. char *    rtext;    /* Replacement text of the macro.        */
  1123. char **    argps;    /* Pointer to fully expanded arguments.        */
  1124. char *    out;    /* Output buffer.                */
  1125. int    max_out;    /* Size of out[].             */
  1126. {
  1127.     int    limit;
  1128.     int    c, i, count;
  1129.     int    arg_num;
  1130.     char    *p, *p2;
  1131.     char    str_buf[MAX_SYMBOL];
  1132.  
  1133.     TRACEPB("substitute", printf("(%s, %d, <%s>, %p, %p, %d)\n",
  1134.         name, nargs, pr_str(rtext), argps, out, max_out));
  1135.  
  1136.     /* Make SURE the output buffer is never over-run. */
  1137.     max_out -= 4;
  1138.     if (max_out < 10) {
  1139.         goto run_on;
  1140.     }
  1141.  
  1142.     /*
  1143.         Put the final replacement text into out[].
  1144.         Replace ARG_FLAG n by arg n.
  1145.         Replace POUND_FLAG n by the stringized arg n.
  1146.         Delete CONCAT_FLAG and surrounding whitespace.
  1147.     */
  1148.  
  1149.     limit = strlen(rtext);
  1150.     count = 0;
  1151.     for (i = 0; i < limit;) {
  1152.  
  1153.         /*
  1154.             Check for run-on expansion.
  1155.             This is actually possible.
  1156.         */
  1157.         if (count >= max_out) {
  1158.             goto run_on;
  1159.         }
  1160.  
  1161.         c = rtext[i++];
  1162.         if (c == ARG_FLAG) {
  1163.  
  1164.             /* Copy the argument. */
  1165.             arg_num = rtext[i++] - ARG_OFFSET;
  1166.             if (arg_num >= nargs) {
  1167.                 /* Previous user error. */
  1168.                 out[count++] = ' ';
  1169.                 continue;
  1170.             }
  1171.  
  1172.             if (count + strlen(argps[arg_num]) >= max_out) {
  1173.                 goto run_on;
  1174.             }
  1175.  
  1176.             str_cpy(out + count, argps [arg_num]);
  1177.             count += strlen(argps[arg_num]);
  1178.         }
  1179.         else if (c == POUND_FLAG) {
  1180.  
  1181.             /* Stringize an actual argument */
  1182.             arg_num = rtext[i++] - ARG_OFFSET;
  1183.             if (arg_num >= nargs) {
  1184.                 /* Previous user error. */
  1185.                 out[count++] = ' ';
  1186.                 continue;
  1187.             }
  1188.             out[count++] = '"';
  1189.             for(p = argps[arg_num]; *p; ) {
  1190.             /* bug fix: 2/14/89 (test for single quote also. */
  1191.             if (*p == '"' || *p == '\'') {
  1192.                 /* Copy the string. */
  1193.                 p += in_string(p, &str_buf[0], MAX_SYMBOL);
  1194.  
  1195.                 /* Stingize the string. */
  1196.                 for (p2 = &str_buf[0]; *p2; ) {
  1197.                     if (count >= max_out-1) {
  1198.                         goto run_on;
  1199.                     }
  1200.                     if (*p2 == '\\' || *p2 == '"') {
  1201.                         out[count++] = '\\';
  1202.                         out[count++] = *p2++;
  1203.                     }
  1204.                     else {
  1205.                         out[count++] = *p2++;
  1206.                     }
  1207.                 }
  1208.             }
  1209.             else {
  1210.                 out[count++] = *p++;
  1211.             }
  1212.             } /* end for */
  1213.             out[count++] = '"';
  1214.         }
  1215.         else if (c == CONCAT_FLAG) {
  1216.  
  1217.             /* Delete preceding whitespace. */
  1218.             while (count-1 >= 0 && out[count-1] == ' ') {
  1219.                 count--;
  1220.             }
  1221.  
  1222.             /* Delete following whitespace. */
  1223.             while (i < limit && rtext[i] == ' ') {
  1224.                 i++;
  1225.             }
  1226.         }
  1227.         else {
  1228.             out[count++] = c;
  1229.         }
  1230.     }
  1231.  
  1232.     /* Append current ch to output. */
  1233.     out[count] = '\0';
  1234.  
  1235.     TRACEP("substitute",
  1236.         printf("count: %d, out: <%s>\n", count, pr_str(out)));
  1237.  
  1238.     RETURN_VOID("substitute");
  1239.  
  1240. run_on:
  1241.     strcpy(str_buf, "Macro expansion for ");
  1242.     strcat(str_buf, name);
  1243.     strcat(str_buf, " is too long...\nExpansion set to empty string");
  1244.     error(str_buf);
  1245.  
  1246.     /* Truncate the ENTIRE macro. */
  1247.     out[0] = '\0';
  1248.     RETURN_VOID("substitute");
  1249. }
  1250.