/****************************************************************************
*
*  This code is Public Domain.
*
*  ========================================================================
*
* Description:  macro handling.
*
* functions:
* - CreateMacro      create a macro item
* - ReleaseMacroData used to redefine/purge a macro
* - StoreMacro       store a macro's parameter/local/line list
* - MacroDir         handle MACRO directive
* - PurgeDirective   handle PURGE directive
* - MacroInit        global macro initialization, set predefined macros
*
****************************************************************************/

#include <ctype.h>

#include "globals.h"
#include "memalloc.h"
#include "parser.h"
#include "input.h"
#include "tokenize.h"
#include "macro.h"
#include "fastpass.h"
#include "listing.h"

/* a placeholder consists of escape char (0x0a) + index (1 byte).
 if this is to change, function fill_placeholders() must
 be adjusted!
 */
#define PLACEHOLDER_SIZE 2

#define MAX_PLACEHOLDERS 256

/* store empty macro lines, to ensure correct line numbering
 */
#define STORE_EMPTY_LINES 1

/* 1="undefine" macros with PURGE - this isn't Masm-compatible,
 * and offers no real benefit because the name remains in the namespace.
 * The macro is marked "undefined" and cannot be invoked anymore.
 * 0=just delete the macro content.
 */
#define TRUEPERGE 0

extern int  MacroLocals;

/* the list of macro param + local names is hold temporarily only.
 * once the names have been replaced by placeholders,
 * the list is superfluous. What's stored permanently
 * in the macro item is the number of params and locals only.
 */
struct mname_list {
    char        *label;         /* name of param/local */
    uint_16     len;
};

#ifdef __WATCOMC__
static _inline char HexDigit( char x )
#elif defined(_MSC_VER)
static _inline char HexDigit( char x )
#else
static char HexDigit( char x )
#endif
{
    x &= 0xF;
    return((x > 9) ? (x - 10 + 'A') : (x + '0'));
}

/* Replace placeholders in a stored macro source line with values of actual
 * parameters and locals. A placeholder consists of escape char 0x0a,
 * followed by a one-byte index field.
 */
void fill_placeholders( char *dst, const char *src, unsigned argc, unsigned localstart, char *argv[] )
/****************************************************************************************************/
{
    uint_32 i;
    const char *p;
    unsigned parmno;

    /* scan the string, replace the placeholders #nn */
    for( p = src ;*p != NULLC; ) {
        if (*p == PLACEHOLDER_CHAR ) {
            p++;
            /* we found a placeholder, get the index part! */
            parmno = *(unsigned char *)p - 1; /* index is one-based! */
            p++;
            /* if parmno > argc, then it's a macro local */
            if ( parmno >= argc ) {
                *dst++ = '?';
                *dst++ = '?';
                i = localstart + parmno - argc;
                if ( i > 0xFFFF ) {
                    i = sprintf( dst, "%X", i );
                    dst += i;
                } else {
                    *dst++ = HexDigit( i >> 12 );
                    *dst++ = HexDigit( i >> 8 );
                    *dst++ = HexDigit( i >> 4 );
                    *dst++ = HexDigit( i );
                }
            } else if ( argv[parmno] ) {  /* actual parameter might be empty (=NULL) */
                i = strlen( argv[parmno] );
                memcpy( dst, argv[parmno], i );
                dst += i;
            }
        } else {
            *dst++ = *p++;
        }
    }
    *dst = NULLC;
    return;
}

static char * replace_parm( const char *line, char *start, int len, struct mname_list *mnames )
/*********************************************************************************************/
{
    /* scan list of macro paras/locals if current word is found.
     * - line: current line
     * - start: start 'current word' in line
     * - len: size current word
     * - mnames: list of macro params+locals
     * if found, the 'current word' is replaced by a placeholder.
     * format of placeholders is <placeholder_char><index>
     * <placeholder_char> is an escape character whose hex-code is
     * "impossible" to occur in a source line, <index> has type uint_8,
     * value 00 isn't used - this restricts the total of parameters
     * and locals for a macro to 255.
     */
    char       *rest;
    unsigned   count;

//  DebugMsg(("replace_parm(%s) enter, len=%u\n", start, len ));

    for( count = 1; mnames->label; count++, mnames++ ) {
        if( mnames->len == len && SymCmpFunc( start, mnames->label, len ) == 0 ) {

            /* found a macro parameter/local! */

            if ( count >= MAX_PLACEHOLDERS ) {
                EmitError( TOO_MANY_MACRO_PLACEHOLDERS );
                break;
            }

            /* handle substitution operator '&' */
            rest = start + len;
            if ( start != line && *(start-1) == '&' )
                start--;
            if (*rest == '&')
                rest++;

            *start++ = PLACEHOLDER_CHAR;

            /* additional space needed for the placeholder? */
            if ( start >= rest ) {
                char *end = rest + strlen(rest);
                char *dst = end + 1;
                while ( end >= rest )
                    *dst-- = *end--;
                *start = count;
            } else {
                *start++ = count;
                /* v2.10: strcpy should not be used if strings overlap */
                //strcpy( start, rest );
                memmove( start, rest, strlen( rest) + 1 );
            }
            return( start ); /* word has been replaced */
        }
    }
    return( NULL );
}

static int store_placeholders( char *line, struct mname_list *mnames )
/********************************************************************/
{
    /* scan a macro source line for parameter and local names.
     * - line: the source line
     * - mnames: list of macro params + locals
     * if a param/local is found, replace the name by a 2-byte placeholder.
     */
    char *p;
    char *start;
    char quote = NULLC;
    int brlevel = 0;
    int params = 0; /* number of replacements in this line */
    int qlevel;
    bool substprf;  /* substitution character before ID? */

    for( p = line; *p != NULLC; ) {
        if ( isdigit( *p) ) {
            /* skip numbers (they may contain alphas)
             * this is not exactly what masm does. Masm
             * stops at the first non-digit.
             */
            while ( is_valid_id_char( *p )) p++;
        } else if ( ( is_valid_id_char( *p ) ) ||
                   ( *p == '.' &&
                    ModuleInfo.dotname &&
                    //is_valid_id_char(*(p+1)) && /* v2.05: masm allows a single dot as param/local name */
                    ( p == line ||
                     ( *(p-1) != ']' && ( is_valid_id_char( *(p-1) ) == FALSE ) ) ) ) ) {
            DebugMsg1(("store_placeholders: found ID: %s\n", p));
            start = p++;
            while ( is_valid_id_char( *p )) p++;
            /* v2.08: both a '&' before AND after the name trigger substitution (and disappear) */
            substprf = ( ( start > line && *(start-1) == '&') || *p == '&' );
            if ( quote == NULLC || substprf ) {
                /* look for this word in the macro parms, and replace it if it is */
                if ( start = replace_parm( line, start, p - start, mnames ) ) {
                    params++;
                    p = start;
                }
            }
        } else {
            switch (*p) {
            case '!':
                /* v2.11: skip next char only if it is a "special" one; see expans40.asm */
                //if ( quote == NULLC && *(p+1) != NULLC )
                if ( quote == NULLC && strchr( "<>\"'", *(p+1) ) )
                    p++;
                break;
            case '<':
                brlevel++;
                break;
            case '>':
                if (brlevel) {
                    if (qlevel == brlevel)
                        quote = NULLC;
                    brlevel--;
                }
                break;
            case '"':
            case '\'':
                if ( quote ) {
                    if ( quote == *p )
                        quote = NULLC;
                } else {
                    quote = *p;
                    qlevel = brlevel;
                }
            }
            p++;
        }
    }
    return( params );
}

#ifdef DEBUG_OUT
char *RenderMacroLine( const char *src )
/**************************************/
{
    /* a macro line cannot be displayed directly due to the format of
     * the index field. for debug log, convert it to a readable format.
     */
    char *dst;
    static char buffer[MAX_LINE_LEN]; /* debug only */

    for ( dst = buffer; *src; src++, dst++ ) {
        if (*src == PLACEHOLDER_CHAR ) {
            *dst++ = '#';
            src++;
            *dst = *src / 16 + '0';
            if (*dst > '9')
                *dst += 7;
            dst++;
            *dst = *src % 16 + '0';
            if (*dst > '9')
                *dst += 7;
        } else
            *dst = *src;
    }
    *dst = NULLC;
    return( buffer );
}
#endif

/*
 * store a macro's parameter, local and content list.
 * i = start index of macro params in token buffer.
 * store_data : if true, store the macro text lines
 */

ret_code StoreMacro( struct dsym *macro, int i, struct asm_tok tokenarray[], bool store_data )
/********************************************************************************************/
{
    struct macro_info   *info;
    char                *src;
    char                *token;
    int                 mindex;
    struct mparm_list   *paranode;
    struct srcline      **nextline;
#ifdef DEBUG_OUT
    int lineno = 0;
#endif
    unsigned            nesting_depth = 0;
    bool                locals_done;
    struct line_status  ls;
    struct asm_tok      tok[2];
    struct mname_list   mnames[MAX_PLACEHOLDERS]; /* there are max 255 placeholders */
    char                buffer[MAX_LINE_LEN];

    DebugMsg1(("StoreMacro(%s, i=%u, store_data=%u) enter, params=>%s<\n", macro->sym.name, i, store_data, tokenarray[i].tokpos ));
    info = macro->e.macroinfo;

    if( store_data ) {
        int j;

        if ( i < Token_Count ) {
            for ( j = i, info->parmcnt = 1; j < Token_Count; j++ )
                if ( tokenarray[j].token == T_COMMA )
                    info->parmcnt++;
            info->parmlist = LclAlloc( info->parmcnt * sizeof(struct mparm_list));
        } else {
            info->parmcnt = 0;
            info->parmlist = NULL;
        }

        for( paranode = info->parmlist, mindex = 0; i < Token_Count ; paranode++ ) {

            token = tokenarray[i].string_ptr;
            /* Masm accepts reserved words and instructions as parameter
             * names! So just check that the token is a valid id.
             */
            if ( !is_valid_id_first_char( *token ) || tokenarray[i].token == T_STRING ) {
                EmitErr( SYNTAX_ERROR_EX, token );
                break;
            } else if ( tokenarray[i].token != T_ID )
                EmitWarn( 4, PARAM_IS_RESERVED_WORD, tokenarray[i].string_ptr );

            paranode->deflt = NULL;
            paranode->required = FALSE;

            /* first get the parm. name */
            j = strlen( token );
            mnames[mindex].label = token;
            mnames[mindex].len = j;
            mindex++;
            mnames[mindex].label = NULL; /* init next entry */
            i++;

            /* now see if it has a default value or is required */
            if( tokenarray[i].token == T_COLON ) {
                i++;
                if( tokenarray[i].token == T_DIRECTIVE && tokenarray[i].dirtype == DRT_EQUALSGN ) {
                    i++;
                    /* allowed syntax is parm:=<literal> */
                    if( tokenarray[i].token != T_STRING || tokenarray[i].string_delim != '<' ) {
                        EmitError( LITERAL_EXPECTED_AFTER_EQ );
                        break; // return( ERROR );
                    }
                    paranode->deflt = LclAlloc( tokenarray[i].stringlen + 1 );
                    memcpy( paranode->deflt, tokenarray[i].string_ptr, tokenarray[i].stringlen + 1 );
                    i++;
                } else if( _stricmp( tokenarray[i].string_ptr, "REQ" ) == 0 ) {
                    /* required parameter */
                    paranode->required = TRUE;
                    i++;
                } else if( tokenarray[i].token == T_RES_ID && tokenarray[i].tokval == T_VARARG ) {
                    /* more parameters can follow */
                    macro->sym.mac_vararg = TRUE;
                    if ( tokenarray[i+1].token != T_FINAL ) {
                        EmitError( VARARG_PARAMETER_MUST_BE_LAST );
                        break;
                    }
                    i++;
#if MACROLABEL
                } else if( tokenarray[i].token == T_DIRECTIVE &&
                          tokenarray[i].tokval == T_LABEL &&
                          Options.strict_masm_compat == FALSE ) { /* parm:LABEL? */
                    /* LABEL attribute for first param only! */
                    if ( paranode != info->parmlist ) {
                        EmitError( LABEL_PARAMETER_MUST_BE_FIRST );
                        break;
                    }
                    macro->sym.label = TRUE;
                    i++;
#endif
#if VARARGML
                } else if( _stricmp( tokenarray[i].string_ptr, "VARARGML" ) == 0 ) {
                    /* more parameters can follow, multi lines possible */
                    macro->sym.mac_vararg = TRUE;
                    macro->sym.mac_multiline = TRUE;
                    if ( tokenarray[i+1].token != T_FINAL ) {
                        EmitError( VARARG_PARAMETER_MUST_BE_LAST );
                        break;
                    }
                    i++;
#endif
                } else {
                    EmitErr( SYNTAX_ERROR_EX, tokenarray[i].string_ptr );
                    break;
                }
            }
            DebugMsg1(("StoreMacro(%s): param=>%s< found\n", macro->sym.name, mnames[mindex].label));
            if( i < Token_Count && tokenarray[i].token != T_COMMA ) {
                EmitErr( SYNTAX_ERROR_EX, tokenarray[i].tokpos );
                break; // return( ERROR );
            }
            /* go past comma */
            i++;

        } /* end for() */
        DebugMsg1(("StoreMacro(%s): macro parameters done\n", macro->sym.name));
    }

    locals_done = FALSE;
    nextline = &info->data;

    /* now read in the lines of the macro, and store them if store_data is TRUE */
    for( ; ; ) {
        char *ptr;

        src = GetTextLine( buffer );
        if( src == NULL ) {
            /* v2.11: fatal error if source ends without an ENDM found */
            //EmitError( UNMATCHED_MACRO_NESTING );
            //ModuleInfo.EndDirFound = TRUE; /* avoid error "END not found" */
            //return( ERROR );
            Fatal( UNMATCHED_MACRO_NESTING );
        }

        /* add the macro line to the listing file */
        /* v2.09: don't make listing depend on store_data */
        //if ( ModuleInfo.list && store_data ) {
        if ( ModuleInfo.list ) {
            ModuleInfo.CurrComment = NULL; /* v2.14: reset comment, since line wasn't preprocessed */
            ModuleInfo.line_flags = 0;
            LstWrite( LSTTYPE_MACROLINE, 0, buffer );
        }
        ls.input = src;
        ls.start = src;
        ls.index = 0;
    continue_scan:
        while ( isspace(*ls.input) ) ls.input++;

        /* skip empty lines! */
        if ( *ls.input == NULLC || *ls.input == ';' ) {
#if STORE_EMPTY_LINES
            if( store_data ) {
                *nextline = LclAlloc( sizeof( struct srcline ) );
                (*nextline)->next = NULL;
                (*nextline)->ph_count = 0;
                (*nextline)->line[0] = NULLC;
                nextline = &(*nextline)->next;
            }
#endif
            continue;
        }

        /* get first token */
        ls.output = StringBufferEnd;
        //ls.last_token = T_FINAL;
        ls.flags = TOK_DEFAULT;
        ls.flags2 = 0;
        tok[0].token = T_FINAL;
        if ( GetToken( &tok[0], &ls ) == ERROR )
            return( ERROR );

        /* v2.05: GetTextLine() doesn't concat lines anymore.
         * So if a backslash is found in the current source line,
         * tokenize it to get possible concatenated lines.
         */
        if ( strchr( ls.input, '\\' ) ) {
            ptr = ls.input;
            while ( *ls.input && *ls.input != ';' ) {
                ls.flags3 = 0;
                GetToken( &tok[1], &ls );
                /* v2.09: don't query store_data */
                //if ( ( ls.flags3 & TF3_ISCONCAT ) && ModuleInfo.list && store_data ) {
                if ( ( ls.flags3 & TF3_ISCONCAT ) && ModuleInfo.list ) {
                    ModuleInfo.line_flags = 0;
                    LstWrite( LSTTYPE_MACROLINE, 0, ls.input );
                }
                while( isspace( *ls.input ) ) ls.input++;
            }
            ls.input = ptr;
        }
        if ( tok[0].token == T_FINAL ) {/* did GetToken() return EMPTY? */
            DebugMsg1(("StoreMacro(%s): no token\n", macro->sym.name));
            goto continue_scan;
        }
        /* handle LOCAL directive(s) */
        if( locals_done == FALSE && tok[0].token == T_DIRECTIVE && tok[0].tokval == T_LOCAL ) {
            if( !store_data )
                continue;
            for ( ;; ) {
                int size;
                while( isspace( *ls.input ) ) ls.input++;
                if ( *ls.input == NULLC || *ls.input == ';' ) /* 0 locals are ok */
                    break;
                ls.output = StringBufferEnd;
                GetToken( &tok[0], &ls );
                if ( !is_valid_id_first_char( *StringBufferEnd ) ) {
                    EmitErr( SYNTAX_ERROR_EX, StringBufferEnd );
                    break;
                } else if ( tok[0].token != T_ID )
                    EmitWarn( 4, PARAM_IS_RESERVED_WORD, StringBufferEnd );

                if ( mindex == ( MAX_PLACEHOLDERS - 1 ) ) {
                    EmitError( TOO_MANY_MACRO_PLACEHOLDERS );
                    break;
                }
                size = strlen( StringBufferEnd );
                mnames[mindex].label = myalloca( size );
                memcpy( mnames[mindex].label, StringBufferEnd, size );
                mnames[mindex].len = size;
                mindex++;
                mnames[mindex].label = NULL; /* mark end of placeholder array */
                info->localcnt++;
                DebugMsg1(("StoreMacro(%s, %u): local=>%s< added, rest=%s\n", macro->sym.name, nesting_depth, mnames[mindex].label, ls.input ));
                while( isspace( *ls.input ) ) ls.input++;
                if ( *ls.input == ',' ) {
                    ls.input++;
                } else if ( is_valid_id_first_char( *ls.input ) ) {
                    EmitErr( SYNTAX_ERROR_EX, ls.input );
                    break;
                }
            }
            continue;
        }
        locals_done = TRUE;

        /* handle macro labels, EXITM, ENDM and macro loop directives.
         * this must be done always, even if store_data is false,
         * to find the matching ENDM that terminates the macro.
         */
        if ( tok[0].token == T_COLON ) { /* macro label? */
            /* skip leading spaces for macro labels! In RunMacro(),
             * the label search routine expects no spaces before ':'.
             */
            src = ls.input - 1;
        } else if( tok[0].token == T_DIRECTIVE ) {
            if ( tok[0].tokval == T_EXITM ) {
                DebugMsg1(("StoreMacro(%s): exitm found, lvl=%u, >%s<\n", macro->sym.name, nesting_depth, ls.input ));
                if ( nesting_depth == 0 ) {
                    ptr = ls.input;
                    while( isspace( *ptr ) ) ptr++;
                    if ( *ptr && *ptr != ';' )
                        macro->sym.isfunc = TRUE;
                    //macro->sym.runsync = TRUE;
                }
            } else if( tok[0].tokval == T_ENDM ) {
                DebugMsg1(("StoreMacro(%s): endm found, lvl=%u\n", macro->sym.name, nesting_depth ));
                if( nesting_depth ) {
                    nesting_depth--;
                } else {
                    break; /* exit the for() loop */
                }
            } else if( tok[0].dirtype == DRT_LOOPDIR ) {
                nesting_depth++; /* FOR[C], IRP[C], REP[EA]T, WHILE */
            }
        } else if ( tok[0].token != T_INSTRUCTION || *ls.input == '&' ) {
            /* Skip any token != directive or instruction (and no '&' attached)
             * might be text macro ids, macro function calls,
             * code labels, ...
             */
            for (;;) {
                char oldc;
                tok[0].token = T_FINAL;
                while ( isspace( *ls.input ) ) ls.input++;
                if ( *ls.input == NULLC || *ls.input == ';' )
                    break;
                oldc = *(ls.input-1);
                if ( GetToken( &tok[0], &ls ) == ERROR )
                    break;
                if ( ( tok[0].token == T_INSTRUCTION || tok[0].token == T_DIRECTIVE ) &&
                    oldc != '&' && *ls.input != '&' )
                    break;
            }
            if ( tok[0].token == T_DIRECTIVE ) {
                /* MACRO or loop directive? */
                if ( tok[0].tokval == T_MACRO || tok[0].dirtype == DRT_LOOPDIR )
                    nesting_depth++;
            }
        }

        /* store the line, but first check for placeholders!
         * this is to be improved. store_placeholders() is too
         * primitive. It's necessary to use the tokenizer.
         */
        if( store_data ) {
            int j;
            uint_8 phs = 0;
            if ( mindex )
                phs = store_placeholders( src, mnames );
            j = strlen( src );
            *nextline = LclAlloc( sizeof( struct srcline ) + j );
            (*nextline)->next = NULL;
            (*nextline)->ph_count = phs;
            memcpy( (*nextline)->line, src, j + 1 );
            nextline = &(*nextline)->next;
            DebugMsg1(("StoreMacro(%s, %u): cnt=%u, %u. line >%s<\n", macro->sym.name, nesting_depth, phs, ++lineno, RenderMacroLine( src ) ));
        }
    } /* end for */
    macro->sym.isdefined = TRUE;
    macro->sym.purged = FALSE;
    DebugMsg1(("StoreMacro(%s): exit, no error, isfunc=%u\n", macro->sym.name, macro->sym.isfunc));
    return( NOT_ERROR );
}

/* create a macro symbol */

struct dsym *CreateMacro( const char *name )
/******************************************/
{
    struct dsym *macro;
    if ( macro = (struct dsym *)SymCreate( name ) ) {
        macro->sym.state = SYM_MACRO;
        macro->e.macroinfo = LclAlloc( sizeof( struct macro_info ) );
        macro->e.macroinfo->parmcnt  = 0;
        macro->e.macroinfo->localcnt = 0;
        macro->e.macroinfo->parmlist = NULL;
        macro->e.macroinfo->data     = NULL;
#ifdef DEBUG_OUT
        macro->e.macroinfo->count = 0;
#endif
        macro->e.macroinfo->srcfile  = 0;
        macro->sym.mac_vararg = FALSE;
        macro->sym.isfunc = FALSE;
    }
    return( macro );
}

/* clear macro data */

void ReleaseMacroData( struct dsym *macro )
/*****************************************/
{
    int             i;
    struct srcline  *datacurr;
    struct srcline  *datanext;

    DebugMsg1(("ReleaseMacroData(%s) enter\n", macro->sym.name));
    /* free the parm list */
    for( i = 0 ; i < macro->e.macroinfo->parmcnt; i++ ) {
        /*
         for predefined macros, don't free the param labels,
         the items are stored in static memory
         */
        //if ( macro->sym.predefined == FALSE )
        //    LclFree( (void *)macro->e.macroinfo->parmlist[i].label );
        LclFree( macro->e.macroinfo->parmlist[i].deflt );
    }

    macro->e.macroinfo->parmcnt = 0;
    macro->e.macroinfo->localcnt = 0;

    if( macro->e.macroinfo->parmlist ) {
        LclFree( macro->e.macroinfo->parmlist );
        macro->e.macroinfo->parmlist = NULL;
    }

    /* free the lines list */
    for( datacurr = macro->e.macroinfo->data ;datacurr; ) {
        datanext = datacurr->next;
        LclFree( datacurr );
        datacurr = datanext;
    }
    macro->e.macroinfo->data = NULL;
    macro->e.macroinfo->srcfile = 0;
    macro->sym.mac_vararg = FALSE;
    /* v2.07: the macro type should not change if a macro is
     * PURGEd.
     */
    //macro->sym.isfunc = FALSE;
    return;
}

/* MACRO directive: define a macro
 * i: directive token ( is to be 1 )
 */
ret_code MacroDir( int i, struct asm_tok tokenarray[] )
/*****************************************************/
{
    char                *name;
    bool                store_data;
    struct dsym         *macro;

    name = tokenarray[0].string_ptr;
    DebugMsg1(("MacroDir(%s) enter, i=%u\n", name, i ));

    macro = (struct dsym *)SymSearch( name );
    if( macro == NULL ) {
        macro = CreateMacro( name );
    } else if( macro->sym.state != SYM_MACRO ) {
        if ( macro->sym.state != SYM_UNDEFINED ) {
            return( EmitErr( SYMBOL_REDEFINITION, name ) );
        }
        /* the macro was used before it's defined. That's
         * a severe error. Nevertheless define the macro now,
         * error msg 'invalid symbol type in expression' will
         * be displayed in second pass when the (unexpanded)
         * macro name is found by the expression evaluator.
         */
        sym_remove_table( &SymTables[TAB_UNDEF], macro );
        macro->sym.state = SYM_MACRO;
        macro->e.macroinfo = LclAlloc( sizeof( struct macro_info ) );
        memset( macro->e.macroinfo, 0, sizeof( struct macro_info ) );
    }
    macro->e.macroinfo->srcfile = get_curr_srcfile();

    if ( ( Parse_Pass == PASS_1 ) || ( macro->sym.isvariable ) ) {
        /* is the macro redefined? */
        if ( macro->e.macroinfo->data != NULL ) {
            DebugMsg(("MacroDir(%s): macro already defined\n", name));
#if FASTMEM==0
            /* don't free memory of macro data lines if macro is in use */
            if ( MacroInUse( macro ) )
                macro->e.macroinfo->data = NULL;
#endif
            ReleaseMacroData( macro );
            /* v2.07: isfunc isn't reset anymore in ReleaseMacroData() */
            macro->sym.isfunc = FALSE;
            macro->sym.isvariable = TRUE;
        }
        store_data = TRUE;
    } else
        store_data = FALSE;

    if ( ModuleInfo.list ) LstWriteSrcLine();

    return( StoreMacro( macro, ++i, tokenarray, store_data ) );
}

/*
 * PURGE directive.
 * syntax: PURGE macro [, macro, ... ]
 * Masm deletes the macro content, but the symbol name isn't released
 * and cannot be used for something else.
 * Text macros cannot be purged, because the PURGE arguments are expanded.
 */
ret_code PurgeDirective( int i, struct asm_tok tokenarray[] )
/***********************************************************/
{
    struct asym *sym;

    i++; /* skip directive */
    do {
        if ( tokenarray[i].token != T_ID ) {
            return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i].string_ptr ) );
        }
        sym = SymSearch( tokenarray[i].string_ptr );
        if ( sym == NULL ) {
            return( EmitErr( SYMBOL_NOT_DEFINED, tokenarray[i].string_ptr ) );
        }
        if ( sym->state != SYM_MACRO ) {
            return( EmitErr( EXPECTED, "macro name" ) );
        }
#if TRUEPURGE
        sym->defined = FALSE;
#else
#if FASTMEM==0
        /* don't free memory of macro data lines if macro is in use */
        if ( MacroInUse( (struct dsym *)sym ) ) {
            DebugMsg1(("PurgeDirective(%s): macro is in use\n", sym->name ));
            ((struct dsym *)sym)->e.macroinfo->data = NULL;
        }
#endif
        ReleaseMacroData( (struct dsym *)sym );
        sym->isvariable = TRUE;
        sym->purged = TRUE;
#endif
        i++;
        if ( i < Token_Count ) {
            if ( tokenarray[i].token != T_COMMA || tokenarray[i+1].token == T_FINAL ) {
                return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i].tokpos ) );
            }
            i++;
        }
    } while ( i < Token_Count );

    return( NOT_ERROR );
}

/* internal @Environ macro function */
/* v2.08: ensured no buffer overflow if environment variable is larger than MAX_LINE_LEN */

static ret_code EnvironFunc( struct macro_instance *mi, char *buffer, struct asm_tok tokenarray[] )
/*************************************************************************************************/
{
    char *p = getenv( mi->parm_array[0] );
    int i;

    buffer[0] = NULLC;
    if ( p ) {
        i = strlen( p );
        if ( i >= MAX_LINE_LEN )
            i = MAX_LINE_LEN - 1;
        memcpy( buffer, p, i );
        buffer[i] = NULLC;
    }
    return( NOT_ERROR );
}

/* macro initialization
 * this proc is called once per pass
 */
ret_code MacroInit( int pass )
/****************************/
{
    struct dsym *macro;

    DebugMsg(( "MacroInit(%u)\n", pass ));

    MacroLevel = 0;
    MacroLocals = 0;
    if (pass == PASS_1) {

        StringInit();

        /* add @Environ() macro func */

        macro = CreateMacro( "@Environ" );
        macro->sym.isdefined = TRUE;
        macro->sym.predefined = TRUE;
        macro->sym.func_ptr = EnvironFunc;
        macro->sym.isfunc = TRUE;
        macro->e.macroinfo->parmcnt = 1;
        macro->e.macroinfo->parmlist = LclAlloc(sizeof(struct mparm_list));
        macro->e.macroinfo->parmlist->deflt = NULL;
        macro->e.macroinfo->parmlist->required = TRUE;
    }
    return( NOT_ERROR );
}
#ifdef DEBUG_OUT
void MacroFini( void )
/********************/
{
    StringFini();
}
#endif
