/*****************************************************************************
 *
 * define.c
 *
 *  Builtins related to object definitions.
 *
 *****************************************************************************/

#include "m4.h"

/*****************************************************************************
 *
 *  opDefineOrPushdef
 *
 *      Common worker for opDefine and opPushdef.
 *
 *      If we are not pushing, then we must pop off the previous value
 *      in order to free its memory, before pushing the new definition.
 *
 *      QUIRK!  GNU m4 emits a warning if $# > 2.  AT&T silently ignores
 *      extra arguments.  I side with AT&T on this one.
 *
 *      QUIRK!  GNU m4 emits `$0' if $# = 0.  AT&T silently ignores
 *      the entire macro call.  I side with GNU on this one.
 *
 *      WARNING!  main.c::DefinePtsz assumes that we don't look at
 *      argv[0] if the correct number of parameters are passed!
 *
 *****************************************************************************/

void STDCALL
opDefineOrPushdef(ARGV argv, BOOL fPush)
{
    if (ctokArgv > 0) {
        /*
         *  Ensure that we don't mess with argv[0].
         */
      D(SIG sigOld = argv[0].sig);
      D(argv[0].sig = 0);
        if (fIdentPtok(ptokArgv(1))) {
            PMAC pmac = pmacGetPtok(ptokArgv(1));
            if (!fPush) {
                if (pmac->pval) {
                    PopdefPmac(pmac);   /* Pop off previous value */
                }
            }
            PushdefPmacPtok(pmac, ptokArgv(2));
#ifdef STRICT_M4
            if (ctokArgv > 2) {
                Warn("extra arguments ignored");
            }
#endif
        } else {
            Die("invalid macro name");
        }
      D(argv[0].sig = sigOld);
    } else {
        PushQuotedPtok(ptokArgv(0));
    }
}

/*****************************************************************************
 *
 *  opDefine
 *
 *      Set the expansion of $1 to $2, destroying any previous value.
 *
 *  opPushdef
 *
 *      Same as opDefine, except pushes the previous value.
 *
 *****************************************************************************/

DeclareOp(opDefine)
{
    opDefineOrPushdef(argv, 0);
}

DeclareOp(opPushdef)
{
    opDefineOrPushdef(argv, 1);
}

/*****************************************************************************
 *
 *  opPopdef
 *
 *      Restores the most recently pushed definition.
 *
 *      If the macro name is invalid, fail silently.
 *
 *****************************************************************************/

DeclareOpc(opcPopdef)
{
    PMAC pmac = pmacFindPtok(ptok);
    if (pmac) {
        Assert(pmac->pval);
        if (pmac->pval->pvalPrev) {
            PopdefPmac(pmac);
        } else {
            FreePmac(pmac);
        }
    }
}

DeclareOp(opPopdef)
{
    EachOpcArgvDw(opcPopdef, argv, 0);
}

/*****************************************************************************
 *
 *  opUndefine
 *
 *      Removes the definitions of all its arguments.
 *
 *****************************************************************************/

DeclareOpc(opcUndefine)
{
    PMAC pmac = pmacFindPtok(ptok);
    if (pmac) {
        FreePmac(pmac);
    }
}

DeclareOp(opUndefine)
{
    EachOpcArgvDw(opcUndefine, argv, 0);
}


/*****************************************************************************
 *
 *  opDefn
 *
 *      Returns the quoted definition of its argument(s), concatenated
 *      from left to right.
 *
 *****************************************************************************/

DeclareOpc(opcDefn)
{
    PMAC pmac = pmacFindPtok(ptok);
    if (pmac) {
        PushQuotedPtok(&pmac->pval->tok);
    }
}

DeclareOp(opDefn)
{
    EachReverseOpcArgvDw(opcDefn, argv, 0);
}