
/*
 *                          C P P 4 . C
 *              M a c r o  D e f i n i t i o n s
 *
 * Edit History
 * 31-Aug-84    MM      USENET net.sources release
 * 04-Oct-84    MM      __LINE__ and __FILE__ must call ungetstring()
 *                      so they work correctly with token concatenation.
 *                      Added string formal recognition.
 * 25-Oct-84    MM      "Short-circuit" evaluate #if's so that we
 *                      don't print unnecessary error messages for
 *                      #if !defined(FOO) && FOO != 0 && 10 / FOO ...
 * 31-Oct-84    ado/MM  Added token concatenation
 *  6-Nov-84    MM      Split off eval stuff
 *  1-Apr-85    ado     Fixed bug in STRING_FORMAL version
 *  2-May-85    MM      Changed the way macro parameters work -- only
 *                      one byte is reserved, 255 param's possible.
 */

#include        <stdio.h>
#include        <ctype.h>
#include        "cppdef.h"
#include        "cpp.h"
#if OK_CONCAT == CON_NOEXPAND
#define SEP     COM_SEP                 /* concat token, don't reexpand */
#endif
#if OK_CONCAT == CON_EXPAND
#define SEP     TOK_SEP                 /* concat token and reexpand    */
#endif

/*
 * parm[], parmp, and parlist[] are used to store #define() argument
 * lists.  nargs contains the actual number of parameters stored.
 */
static char     parm[NPARMWORK + 1];    /* define param work buffer     */
static char     *parmp;                 /* Free space in parm           */
static char     *parlist[NMACPARS];     /* -> start of each parameter   */
static int      nargs;                  /* Parameters for this macro    */

dodefine()
/*
 * Called from control when a #define is scanned.  This module
 * parses formal parameters and the replacement string.  When
 * the formal parameter name is encountered in the replacement
 * string, it is replaced by a character in the range 128 to
 * 128+NPARAM (this allows up to 32 parameters within the
 * Dec Multinational range).  If cpp is ported to an EBCDIC
 * machine, you will have to make other arrangements.
 *
 * There is some special case code to distinguish
 *      #define foo     bar
 * from #define foo()   bar
 *
 * Also, we make sure that
 *      #define foo     foo
 * expands to "foo" but doesn't put cpp into an infinite loop.
 *
 * A warning message is printed if you redefine a symbol to a
 * different text.  I.e,
 *      #define foo     123
 *      #define foo     123
 * is ok, but
 *      #define foo     123
 *      #define foo     +123
 * is not.
 *
 * The following subroutines are called from define():
 * checkparm    called when a token is scanned.  It checks through the
 *              array of formal parameters.  If a match is found, the
 *              token is replaced by a control byte which will be used
 *              to locate the parameter when the macro is expanded.
 * textput      puts a string in the macro work area (parm[]), updating
 *              parmp to point to the first free byte in parm[].
 *              textput() tests for work buffer overflow.
 * charput      puts a single character in the macro work area (parm[])
 *              in a manner analogous to textput().
 */
{
        register int            c;
        register DEFBUF         *dp;            /* -> new definition    */
        int                     isredefine;     /* TRUE if redefined    */
        char                    *old;           /* Remember redefined   */
        extern int              save();         /* Save char in work[]  */

        if (type[(c = skipws())] != LET)
            goto bad_define;
        isredefine = FALSE;                     /* Set if redefining    */
        if ((dp = lookid(c)) == NULL)           /* If not known now     */
            dp = defendel(token, FALSE);        /* Save the name        */
        else {                                  /* It's known:          */
            isredefine = TRUE;                  /* Remember this fact   */
            old = dp->repl;                     /* Remember replacement */
            dp->repl = NULL;                    /* No replacement now   */
        }
        parlist[0] = parmp = parm;              /* Setup parm buffer    */
        if ((c = get()) == '(') {               /* With arguments?      */
            nargs = 0;                          /* Init formals counter */
            do {                                /* Collect formal parms */
                if (nargs >= NMACPARS)
                    cfatal("Too many arguments for macro", NULLST);
                else if ((c = skipws()) == ')')
                    break;                      /* Got them all         */
                else if (type[c] != LET)        /* Bad formal syntax    */
                    goto bad_define;
                scanid(c);                      /* Get the formal param */
                parlist[nargs++] = parmp;       /* Save its start       */
                textput(token);                 /* Save text in parm[]  */
            } while ((c = skipws()) == ',');    /* Get another argument */
            if (c != ')')                       /* Must end at )        */
                goto bad_define;
            c = ' ';                            /* Will skip to body    */
        }
        else {
            /*
             * DEF_NOARGS is needed to distinguish between
             * "#define foo" and "#define foo()".
             */
            nargs = DEF_NOARGS;                 /* No () parameters     */
        }
        if (type[c] == SPA)                     /* At whitespace?       */
            c = skipws();                       /* Not any more.        */
        workp = work;                           /* Replacement put here */
        inmacro = TRUE;                         /* Keep \<newline> now  */
        for (; c != EOF_CHAR && c != '\n'; c = get()) {
#if OK_CONCAT != CON_FALSE
            if (c == '#') {                     /* String/concat?       */
                if ((c = get()) == '#') {       /* Concatenate tokens?  */
                    while (workp > work && type[workp[-1]] == SPA)
                        --workp;                /* Erase leading spaces */
                    save(SEP);
                    c = skipws();               /* Eat whitespace       */
                    switch (type[c]) {          /* What flavor of token */
                    case LET:
                        checkparm(c, dp);       /* Save it normally     */
                        break;

                    case DIG:
                        do {                    /* Stuff the digits     */
                            save(c);
                            c = get();
                        } while (type[c] == DIG);
                        break;

                    default:
                        ciwarn("Strange character after # (%d.)", c);
                        save(c);
                        break;
                    }
                    save(SEP);                  /* Delimit 2nd token    */
                }
                else {                          /* Stringize            */
                    unget();                    /* Gotta rescan it      */
                    /*
                     * We store a magic cookie (which becomes " on output)
                     * so the macro expander doesn't block expansion
                     * of the actual parameter.  For example,
                     *          #define abc(a) #a
                     *          abc(__LINE__)
                     * should yield "123", not "__LINE__".
                     * This is a hack, and probably going to cause trouble.
                     */
                    save(ST_QUOTE);
                    if ((c = isformal(skipws())) == 0) {
                        cwarn("Expected formal parameter, got \"%s\"", token);
                        mtokensave(dp);
                    }
                    save(ST_QUOTE);
                }
                continue;                       /* Done with this token */
            }
#endif
            switch (type[c]) {
            case LET:
                checkparm(c, dp);               /* Might be a formal    */
                break;

            case DIG:                           /* Number in mac. body  */
            case DOT:                           /* Maybe a float number */
                scannumber(c, save);            /* Scan it off          */
                break;

            case QUO:                           /* String in mac. body  */
#if STRING_FORMAL
                stparmscan(c, dp);              /* Do string magic      */
#else
                stparmscan(c);
#endif
                break;

            case BSH:                           /* Backslash            */
                save('\\');
                if ((c = get()) == '\n')
                    wrongline = TRUE;
                save(c);
                break;

            case SPA:                           /* Absorb whitespace    */
                /*
                 * Note: the "end of comment" marker is passed on
                 * to allow comments to separate tokens.
                 */
                if (workp[-1] == ' ')           /* Absorb multiple      */
                    break;                      /* spaces               */
                else if (c == '\t')
                    c = ' ';                    /* Normalize tabs       */
                /* Fall through to store character                      */
            default:                            /* Other character      */
                save(c);
                break;
            }
        }
        inmacro = FALSE;                        /* Stop newline hack    */
        unget();                                /* For control check    */
        if (workp > work && workp[-1] == ' ')   /* Drop trailing blank  */
            workp--;
        *workp = EOS;                           /* Terminate work       */
        dp->repl = savestring(work);            /* Save the string      */
        dp->nargs = nargs;                      /* Save arg count       */
#if DEBUG
        if (debug)
            dumpadef("macro definition", dp);
#endif
        if (isredefine) {                       /* Error if redefined   */
            if ((old != NULL && dp->repl != NULL && !streq(old, dp->repl))
             || (old == NULL && dp->repl != NULL)
             || (old != NULL && dp->repl == NULL)) {
                cerror("Redefining defined variable \"%s\"", dp->name);
            }
            if (old != NULL)                    /* We don't need the    */
                free(old);                      /* old definition now.  */
        }
        return;

bad_define:
        cerror("#define syntax error", NULLST);
        inmacro = FALSE;                        /* Stop <newline> hack  */
}








checkparm(c, dp)
register int    c;
DEFBUF          *dp;
/*
 * Replace this param if it's defined.  Note that the macro name is a
 * possible replacement token.  We stuff DEF_MAGIC in front of the token
 * which is treated as a LETTER by the token scanner and eaten by
 * the output routine.  This prevents the macro expander from
 * looping if someone writes "#define foo foo".
 */
{
        if ((c = isformal(c)) == 0)
            mtokensave(dp);
}

FILE_LOCAL int
isformal(c)
register int    c;
/*
 * Scan the token starting with c.  If it is a formal parameter, save
 * the MAC_PARM and formal offset, returning TRUE.  Else, return FALSE.
 */
{
        register int    i;

        scanid(c);                              /* Get parm to token[]  */
        for (i = 0; i < nargs; i++) {           /* For each argument    */
            if (streq(parlist[i], token)) {     /* If it's known        */
                save(MAC_PARM);                 /* Save the signal      */
                save(i + 1);                    /* Save the formal      */
                return (TRUE);                  /* Return "gotcha"      */
            }
        }
        return (FALSE);                         /* Not a formal param   */
}

FILE_LOCAL
mtokensave(dp)
DEFBUF          *dp;
/*
 * Save the token in the macro buffer.  A magic cookie is saved
 * if the token is identical to the macro name, so the expansion
 * doesn't recurse.
 */
{
        register char           *cp;

        if (dp != NULL && streq(dp->name, token)) /* Macro name in body */
            save(DEF_MAGIC);                    /* Save magic marker    */
        for (cp = token; *cp != EOS;)           /* And save             */
            save(*cp++);                        /* The token itself     */
}




















#if STRING_FORMAL
stparmscan(delim, dp)
int             delim;
register DEFBUF *dp;
/*
 * Scan the string (starting with the given delimiter).
 * The token is replaced if it is the only text in this string or
 * character constant.  The algorithm follows checkparm() above.
 * Note that scanstring() has approved of the string.
 */
{
        register int            c;

        /*
         * Warning -- this code hasn't been tested for a while.
         * It exists only to preserve compatibility with earlier
         * implementations of cpp.  It is not part of the Draft
         * ANSI Standard C language.
         */
        save(delim);
        instring = TRUE;
        while ((c = get()) != delim
             && c != '\n'
             && c != EOF_CHAR) {
            if (type[c] == LET)                 /* Maybe formal parm    */
                checkparm(c, (DEFBUF *) NULL);  /* But no DEF_MAGIC     */
            else {
                save(c);
                if (c == '\\')
                    save(get());
            }
        }
        instring = FALSE;
        if (c != delim)
            cerror("Unterminated string in macro body", NULLST);
        save(c);
}
#else
stparmscan(delim)
int             delim;
/*
 * Normal string parameter scan.
 */
{
        register char           *wp;
        register int            i;
        extern int              save();

        wp = workp;                     /* Here's where it starts       */
        if (!scanstring(delim, save))
            return;                     /* Exit on scanstring error     */
#if 0 && STRING_FORMAL
        /*
         * This code -- if reenabled -- recognizes a formal parameter
         * if it is the only component of a string:
         *      #define foo(bar, v) printf("%" "bar" "\n", v);
         * This has been superceded by # stringizing.
         */
        workp[-1] = EOS;                /* Erase trailing quote         */
        wp++;                           /* -> first string content byte */
#if (NMACPARS * 2) + 1 > 255
    << error, the following won't work >>
#endif
        for (i = 0; i < nargs; i++) {
            if (streq(parlist[i], wp)) {
                *wp++ = MAC_PARM;               /* Parameter signal     */
                *wp++ = i + NMACPARS + 1;       /* Out of range marker  */
                *wp = wp[-3];                   /* Add on closing quote */
                workp = wp + 1;                 /* Reset string end     */
                return;
            }
        }
        workp[-1] = wp[-1];             /* Nope, reset end quote.       */
#endif
}
#endif




















doundef()
/*
 * Remove the symbol from the defined list.
 * Called from the #control processor.
 */
{
        register int            c;

        if (type[(c = skipws())] != LET)
            cerror("Illegal #undef argument", NULLST);
        else {
            scanid(c);                          /* Get name to token[]  */
            if (defendel(token, TRUE) == NULL) {
                cwarn("Symbol \"%s\" not defined in #undef", token);
            }
        }
}

textput(text)
char            *text;
/*
 * Put the string in the parm[] buffer.
 */
{
        register int    size;

        size = strlen(text) + 1;
        if ((parmp + size) >= &parm[NPARMWORK])
            cfatal("Macro work area overflow", NULLST);
        else {
            strcpy(parmp, text);
            parmp += size;
        }
}

charput(c)
register int    c;
/*
 * Put the byte in the parm[] buffer.
 */
{
        if (parmp >= &parm[NPARMWORK])
            cfatal("Macro work area overflow", NULLST);
        else {
            *parmp++ = c;
        }
}

























/*
 *              M a c r o   E x p a n s i o n
 */

static DEFBUF   *macro;         /* Catches start of infinite macro      */

expand(tokenp)
register DEFBUF *tokenp;
/*
 * Expand a macro.  Called from the cpp mainline routine (via subroutine
 * macroid()) when a token is found in the symbol table.  It calls
 * expcollect() to parse actual parameters, checking for the correct number.
 * It then creates a "file" containing a single line containing the
 * macro with actual parameters inserted appropriately.  This is
 * "pushed back" onto the input stream.  (When the get() routine runs
 * off the end of the macro line, it will dismiss the macro itself.)
 */
{
        register int            c;
        register FILEINFO       *file;
        extern FILEINFO         *getfile();

#if DEBUG
        if (debug) {
            dumpadef("expand entry", tokenp);
            dumpunget("expand entry");
        }
#endif
        /*
         * If no macro is pending, save the name of this macro
         * for an eventual error message.
         */
        if (recursion++ == 0)
            macro = tokenp;
        else if (recursion == RECURSION_LIMIT) {
            cerror("Recursive macro definition of \"%s\"", tokenp->name);
            fprintf(stderr, "(Defined by \"%s\")\n", macro->name);
            if (rec_recover) {
                do {
                    c = get();
                } while (infile != NULL && infile->fp == NULL);
                unget();
                recursion = 0;
                return;
            }
        }
        /*
         * Here's a macro to expand.
         */
        nargs = 0;                              /* Formals counter      */
        parmp = parm;                           /* Setup parm buffer    */
        switch (tokenp->nargs) {
        case (-2):                              /* __LINE__             */
            sprintf(work, "%d", line);
            ungetstring(work);
            break;

        case (-3):                              /* __FILE__             */
            for (file = infile; file != NULL; file = file->parent) {
                if (file->fp != NULL) {
                    sprintf(work, "\"%s\"", (file->progname != NULL)
                        ? file->progname : file->filename);
                    ungetstring(work);
                    break;
                }
            }
            break;

        default:
            /*
             * Nothing funny about this macro.
             */
            if (tokenp->nargs < 0)
                cfatal("Bug: Illegal __ macro \"%s\"", tokenp->name);
            while ((c = skipws()) == '\n')      /* Look for (, skipping */
                wrongline = TRUE;               /* spaces and newlines  */
            if (c != '(') {
                /*
                 * If the programmer writes
                 *      #define foo() ...
                 *      ...
                 *      foo [no ()]
                 * just write foo to the output stream.
                 */
                unget();
                cwarn("Macro \"%s\" needs arguments", tokenp->name);
                fputs(tokenp->name, stdout);
                return;
            }
            else if (expcollect()) {            /* Collect arguments    */
                if (tokenp->nargs != nargs) {   /* Should be an error?  */
                    cwarn("Wrong number of macro arguments for \"%s\"",
                        tokenp->name);
                }
#if DEBUG
                if (debug)
                    dumpparm("expand");
#endif
            }                           /* Collect arguments            */
        case DEF_NOARGS:                /* No parameters just stuffs    */
            expstuff(tokenp);           /* Do actual parameters         */
        }                               /* nargs switch                 */
#if DEBUG
        if (debug)
            dumpunget("expand exit");
#endif
}













FILE_LOCAL int
expcollect()
/*
 * Collect the actual parameters for this macro.  TRUE if ok.
 */
{
        register int    c;
        register int    paren;                  /* For embedded ()'s    */
        extern int      charput();

        for (;;) {
            paren = 0;                          /* Collect next arg.    */
            while ((c = skipws()) == '\n')      /* Skip over whitespace */
                wrongline = TRUE;               /* and newlines.        */
            if (c == ')') {                     /* At end of all args?  */
                /*
                 * Note that there is a guard byte in parm[]
                 * so we don't have to check for overflow here.
                 */
                *parmp = EOS;                   /* Make sure terminated */
                break;                          /* Exit collection loop */
            }
            else if (nargs >= NMACPARS)         /* Should be an error   */
                cfatal("Too many actual parameters in macro expansion", NULLST);            parlist[nargs++] = parmp;           /* At start of new arg  */
            for (;; c = cget()) {               /* Collect arg's bytes  */
                if (c == EOF_CHAR) {
                    cerror("end of file within macro argument", NULLST);
                    return (FALSE);             /* Sorry.               */
                }
                else if (c == '\\') {           /* Quote next character */
                    charput(c);                 /* Save the \ for later */
                    charput(cget());            /* Save the next char.  */
                    continue;                   /* And go get another   */
                }
                else if (type[c] == QUO) {      /* Start of string?     */
                    scanstring(c, charput);     /* Scan it off          */
                    continue;                   /* Go get next char     */
                }
                else if (c == '(')              /* Worry about balance  */
                    paren++;                    /* To know about commas */
                else if (c == ')') {            /* Other side too       */
                    if (paren == 0) {           /* At the end?          */
                        unget();                /* Look at it later     */
                        break;                  /* Exit arg getter.     */
                    }
                    paren--;                    /* More to come.        */
                }
                else if (c == ',' && paren == 0) /* Comma delimits args */
                    break;
                else if (c == '\n')             /* Newline inside arg?  */
                    wrongline = TRUE;           /* We'll need a #line   */
                charput(c);                     /* Store this one       */
            }                                   /* Collect an argument  */
            charput(EOS);                       /* Terminate argument   */
#if DEBUG
            if (debug)
                printf("parm[%d] = \"%s\"\n", nargs, parlist[nargs - 1]);
#endif
        }                                       /* Collect all args.    */
        return (TRUE);                          /* Normal return        */
}










FILE_LOCAL
expstuff(tokenp)
DEFBUF          *tokenp;                /* Current macro being expanded */
/*
 * Stuff the macro body, replacing formal parameters by actual parameters.
 */
{
        register int    c;                      /* Current character    */
        register char   *inp;                   /* -> repl string       */
        register char   *defp;                  /* -> macro output buff */
        int             size;                   /* Actual parm. size    */
        char            *defend;                /* -> output buff end   */
#if 0 && STRING_FORMAL
        int             string_magic;           /* String formal hack   */
#endif
        FILEINFO        *file;                  /* Funny #include       */
        extern FILEINFO *getfile();

        file = getfile(NBUFF, tokenp->name);
        inp = tokenp->repl;                     /* -> macro replacement */
        defp = file->buffer;                    /* -> output buffer     */
        defend = defp + (NBUFF - 1);            /* Note its end         */
        if (inp != NULL) {
            while ((c = (*inp++ & 0xFF)) != EOS) {
                if (c == MAC_PARM) {
                    c = (*inp++ & 0xFF) - 1;    /* Parm number          */
#if 0 && STRING_FORMAL
                    string_magic = ((c >= NMACPARS) != 0);
                    if (string_magic)
                        c -= NMACPARS;
#endif
                    /*
                     * Replace formal parameter by actual parameter string.
                     */
                    if (c < nargs) {
                        size = strlen(parlist[c]);
                        if ((defp + size) >= defend)
                            goto nospace;
#if 0 && STRING_FORMAL
                        /*
                         * Erase the extra set of quotes.
                         */
                        if (string_magic && defp[-1] == parlist[c][0]) {
                            strcpy(defp-1, parlist[c]);
                            defp += (size - 2);
                        }
                        else {
                            strcpy(defp, parlist[c]);
                            defp += size;
                        }
#else
                        strcpy(defp, parlist[c]);
                        defp += size;
#endif
                    }
                }
                else if (defp >= defend) {
nospace:            cfatal("Out of space in macro \"%s\" arg expansion",
                        tokenp->name);
                }
                else {
                    *defp++ = c;
                }
            }
        }
        *defp = EOS;
#if DEBUG
        if (debug > 1)
            printf("macroline: \"%s\"\n", file->buffer);
#endif
}

























#if DEBUG
dumpparm(why)
char            *why;
/*
 * Dump parameter list.
 */
{
        register int    i;

        printf("dump of %d parameters (%d bytes total) %s\n",
            nargs, parmp - parm, why);
        for (i = 0; i < nargs; i++) {
            printf("parm[%d] (%d) = \"%s\"\n",
                i + 1, strlen(parlist[i]), parlist[i]);
        }
}
#endif

