/**************************************************************************** * * 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 #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 * is an escape character whose hex-code is * "impossible" to occur in a source line, 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 )) != NULL ) { 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. */ 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:= */ 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.line_flags &= ~LOF_LISTED; 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 &= ~LOF_LISTED; 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 )) != NULL ) { 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.variable ) ) { /* 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.variable = 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->variable = 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