/**************************************************************************** * * Open Watcom Project * * Portions Copyright (c) 1983-2002 Sybase, Inc. All Rights Reserved. * * ======================================================================== * * This file contains Original Code and/or Modifications of Original * Code as defined in and that are subject to the Sybase Open Watcom * Public License version 1.0 (the 'License'). You may not use this file * except in compliance with the License. BY USING THIS FILE YOU AGREE TO * ALL TERMS AND CONDITIONS OF THE LICENSE. A copy of the License is * provided with the Original Code and Modifications, and is also * available at www.sybase.com/developer/opensource. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND SYBASE AND ALL CONTRIBUTORS HEREBY DISCLAIM * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR * NON-INFRINGEMENT. Please see the License for the specific language * governing rights and limitations under the License. * * ======================================================================== * * Description: JWasm conditional processing routines. Handles directives * [ELSE]IF[E], ELSE, ENDIF * [ELSE]IFB, [ELSE]IFNB * [ELSE]IFDEF, [ELSE]IFNDEF * [ELSE]IFDIF[I], [ELSE]IFIDN[I] * [ELSE]IF1, [ELSE]IF2 * .ERR, .ERRNZ, .ERRE * .ERRB, .ERRNB * .ERRDEF, .ERRNDEF * .ERRDIF[I], .ERRIDN[I] * .ERR1, .ERR2 ****************************************************************************/ #include #include "globals.h" #include "parser.h" #include "condasm.h" #include "reswords.h" #include "expreval.h" #include "listing.h" #include "input.h" #include "macro.h" #include "types.h" #include "fastpass.h" /* the current if-block can be in one of 3 states: state assembly possible state change to --------------------------------------------------------- inactive off active active on done done off - -------------------------------------------------------- up to v2.04, there was a fourth state: condition check on active, inactive it was necessary because lines may have been tokenized multiple times. */ enum if_state CurrIfState; static int blocknestlevel; static int falseblocknestlevel; static uint_32 elseoccured; /* 2.06: bit field, magnitude must be >= MAX_IF_NESTING */ #ifdef DEBUG_OUT static const char *GetCurrIfStatString( void ) /********************************************/ { switch ( CurrIfState ) { case BLOCK_ACTIVE: return( "BLOCK_ACTIVE" ); case BLOCK_INACTIVE: return( "BLOCK_INACTIVE" ); default: return( "BLOCK_DONE" ); } } #endif /* * this code runs after the first token has been scanned, * if it is a IFx, ELSEx or ENDIF. * updates variables and . */ void conditional_assembly_prepare( int directive ) /************************************************/ { DebugMsg1(("condasm_prepare(%s), old status: %s, lvl=%u, falselvl=%u\n", GetResWName( directive, NULL), GetCurrIfStatString(), blocknestlevel, falseblocknestlevel)); switch( directive ) { case T_IF: case T_IF1: case T_IF2: case T_IFB: case T_IFDEF: case T_IFDIF: case T_IFDIFI: case T_IFE: case T_IFIDN: case T_IFIDNI: case T_IFNB: case T_IFNDEF: if( CurrIfState != BLOCK_ACTIVE ) { falseblocknestlevel++; break; } if( blocknestlevel == MAX_IF_NESTING ) { EmitError( NESTING_LEVEL_TOO_DEEP ); break; } elseoccured &= ~( 1 << blocknestlevel ); /* v2.06: added */ blocknestlevel++; break; case T_ELSE: case T_ELSEIF: case T_ELSEIF1: case T_ELSEIF2: case T_ELSEIFB: case T_ELSEIFDEF: case T_ELSEIFDIF: case T_ELSEIFDIFI: case T_ELSEIFE: case T_ELSEIFIDN: case T_ELSEIFIDNI: case T_ELSEIFNB: case T_ELSEIFNDEF: if ( blocknestlevel ) { /* v2.04: do nothing if there was no IFx */ if ( falseblocknestlevel > 0 ) { break; } /* v2.06: check added to detect multiple ELSE branches */ if ( elseoccured & ( 1 << ( blocknestlevel - 1 ) ) ) { EmitError( ELSE_CLAUSE_ALREADY_OCCURED_IN_THIS_IF_BLOCK ); break; } /* status may change: * inactive -> active * active -> done */ CurrIfState = (( CurrIfState == BLOCK_INACTIVE ) ? BLOCK_ACTIVE : BLOCK_DONE ); /* v2.06: no further ELSEx once ELSE was detected */ if ( directive == T_ELSE ) elseoccured |= ( 1 << ( blocknestlevel - 1 ) ); } else { EmitErr( BLOCK_NESTING_ERROR, GetResWName( directive, NULL ) ); } break; case T_ENDIF: if ( blocknestlevel ) { if ( falseblocknestlevel > 0 ) { falseblocknestlevel--; break; } blocknestlevel--; CurrIfState = BLOCK_ACTIVE; /* v2.04: added */ } else { EmitErr( BLOCK_NESTING_ERROR, GetResWName( directive, NULL ) ); } break; } DebugMsg1(("condasm_prepare(%s), new status: %s, lvl=%u, falselvl=%u\n", GetResWName( directive, NULL ), GetCurrIfStatString(), blocknestlevel, falseblocknestlevel)); return; } /* handle [ELSE]IF[N]DEF * is * - the value of a T_ID item! * - "" (item is T_FINAL) */ static bool check_defd( const char *name ) /****************************************/ { struct asym *sym; if ( *name ) { sym = SymSearch( name ); if( sym ) { DebugMsg1(("check_defd(%s): state=%u defined=%u\n", name, sym->state, sym->isdefined )); /* v2.04: changed. the "defined" flag is active for ALL symbols */ //if ( sym->state == SYM_INTERNAL || sym->state == SYM_MACRO || sym->state == SYM_TMACRO || sym->state == SYM_UNDEFINED ) { return( sym->isdefined ); } DebugMsg1(("check_defd(%s): sym=NULL\n", name )); } return( FALSE ); } /* handle [ELSE]IF[N]B */ static bool check_blank( const char *string ) /*******************************************/ { for ( ;*string; string++ ) if ( isspace(*string) == FALSE ) return FALSE; return( TRUE ); } /* Are two strings different? * Used by [ELSE]IFDIF[I] and [ELSE]IFIDN[I] */ static bool check_dif( const char *string1, const char *string2, bool sensitive ) /*******************************************************************************/ { if( sensitive ) { return( strcmp( string1, string2 ) != 0 ); } else { return( _stricmp( string1, string2 ) != 0 ); } } ret_code CondAsmDirective( int i, struct asm_tok tokenarray[] ) /*************************************************************/ { int directive = tokenarray[i].tokval; const char *string1; const char *string2; enum if_state NextIfState; struct expr opndx; if ( CurrIfState != BLOCK_ACTIVE ) { DebugMsg1(("CondAsmDirective(%s), CurrIfState=%u(%s), lvl=%u, falselvl=%u\n", GetResWName(directive, NULL), CurrIfState, GetCurrIfStatString(), blocknestlevel, falseblocknestlevel)); if ( i || ModuleInfo.listif ) { LstWriteSrcLine(); } return( NOT_ERROR ); } if ( ModuleInfo.list == TRUE ) { if ( MacroLevel == 0 || ModuleInfo.list_macro == LM_LISTMACROALL || ModuleInfo.listif ) LstWriteSrcLine(); } DebugMsg1(("CondAsmDirective(%s), BLOCK_ACTIVE, lvl=%u, falselvl=%u [%s]\n", GetResWName(directive, NULL), blocknestlevel, falseblocknestlevel, tokenarray[i].tokpos )); i++; /* go past IFx, ELSEx, ENDIF */ /* check params and call appropriate test routine */ switch( GetSflagsSp(directive) ) { case CC_NUMARG: /* [ELSE]IF[E] */ /* no forward reference allowed, symbol must be defined */ if ( ( ERROR == EvalOperand( &i, tokenarray, Token_Count, &opndx, EXPF_NOUNDEF ) ) ) { DebugMsg(("CondAsmDirective(%s), EvalOperand returned with ERROR\n", GetResWName(directive, NULL) )); /* v2.11: don't exit, assume 0 */ //return( ERROR ); opndx.kind = EXPR_CONST; opndx.value = 0; i = Token_Count; } #if 0 /* v2.05: obsolete */ if ( opndx.sym && opndx.sym->state == SYM_UNDEFINED ) { EmitErr( SYMBOL_NOT_DEFINED, opndx.sym->name ); } else #endif if ( opndx.kind == EXPR_CONST ) ; else if ( opndx.kind == EXPR_ADDR && opndx.indirect == FALSE ) { opndx.value += opndx.sym->offset; /* v2.07: Masm doesn't accept a relocatable item, * so emit at least a warning! */ EmitWarn( 2, CONSTANT_EXPECTED ); } else { return( EmitError( CONSTANT_EXPECTED ) ); } if ( directive == T_IF || directive == T_ELSEIF ) NextIfState = ( opndx.value ) ? BLOCK_ACTIVE : BLOCK_INACTIVE; else NextIfState = ( !opndx.value ) ? BLOCK_ACTIVE : BLOCK_INACTIVE; break; case CC_LITARG: /* [ELSE]IFDIF[I], [ELSE]IFIDN[I] */ string1 = tokenarray[i].string_ptr; if ( tokenarray[i].token != T_STRING || tokenarray[i].string_delim != '<' ) { if ( tokenarray[i].token == T_ID && SymSearch( string1 ) == NULL ) EmitErr( SYMBOL_NOT_DEFINED, string1 ); else EmitError( TEXT_ITEM_REQUIRED ); return( ERROR ); } i++; if ( tokenarray[i].token != T_COMMA ) { return( EmitErr( EXPECTING_COMMA, tokenarray[i].tokpos ) ); } i++; string2 = tokenarray[i].string_ptr; if ( tokenarray[i].token != T_STRING || tokenarray[i].string_delim != '<' ) { if ( tokenarray[i].token == T_ID && SymSearch( string2 ) == NULL ) EmitErr( SYMBOL_NOT_DEFINED, string2 ); else EmitError( TEXT_ITEM_REQUIRED ); return( ERROR ); } i++; DebugMsg1(("CondAsmDirective(%s), cmp >%s< and >%s<\n", GetResWName(directive, NULL), string1, string2 )); switch ( directive ) { case T_IFDIF: case T_ELSEIFDIF: NextIfState = check_dif( string1, string2, TRUE ) ? BLOCK_ACTIVE : BLOCK_INACTIVE; break; case T_IFDIFI: case T_ELSEIFDIFI: NextIfState = check_dif( string1, string2, FALSE ) ? BLOCK_ACTIVE : BLOCK_INACTIVE; break; case T_IFIDN: case T_ELSEIFIDN: NextIfState = !check_dif( string1, string2, TRUE ) ? BLOCK_ACTIVE : BLOCK_INACTIVE; break; default: NextIfState = !check_dif( string1, string2, FALSE ) ? BLOCK_ACTIVE : BLOCK_INACTIVE; } break; case CC_BLKARG: /* [ELSE]IF[N]B */ string1 = tokenarray[i].string_ptr; if ( tokenarray[i].token != T_STRING || tokenarray[i].string_delim != '<' ) { if ( tokenarray[i].token == T_ID && SymSearch( string1 ) == NULL ) EmitErr( SYMBOL_NOT_DEFINED, string1 ); else EmitError( TEXT_ITEM_REQUIRED ); return( ERROR ); } i++; if ( directive == T_IFB || directive == T_ELSEIFB ) { NextIfState = check_blank( string1 ) ? BLOCK_ACTIVE : BLOCK_INACTIVE; } else { NextIfState = !check_blank( string1 ) ? BLOCK_ACTIVE : BLOCK_INACTIVE; } break; case CC_PASS1: /* [ELSE]IF1 */ /* v2.04: changed */ //NextIfState = ((Parse_Pass == PASS_1) ? BLOCK_ACTIVE : BLOCK_INACTIVE); NextIfState = BLOCK_ACTIVE; break; case CC_PASS2: /* [ELSE]IF2 */ if ( ModuleInfo.setif2 == FALSE ) { EmitError( IF2_NOT_ALLOWED ); break; } /* v2.04: changed */ //NextIfState = ((Parse_Pass == PASS_1) ? BLOCK_INACTIVE : BLOCK_ACTIVE); NextIfState = BLOCK_ACTIVE; break; case CC_SYMARG: /* [ELSE]IF[N]DEF */ NextIfState = BLOCK_INACTIVE; /* Masm's implementation works with IDs as arguments only. The rest * will return FALSE. However, it's nice to be able to check whether * a reserved word is defined or not. */ /* v2.0: [ELSE]IF[N]DEF is valid *without* an argument! */ //if ( tokenarray[i].token == T_ID && tokenarray[i+1].token == T_FINAL) { if ( tokenarray[i].token == T_FINAL ) { } else if ( tokenarray[i].token == T_ID ) { /* v2.07: handle structs + members (if -Zne is NOT set) */ struct asym *sym; if ( Options.strict_masm_compat == FALSE && tokenarray[i+1].token == T_DOT && ( sym = SymSearch( tokenarray[i].string_ptr ) ) && ( ( sym->state == SYM_TYPE ) || sym->type ) ) { uint_32 value; value = 0; do { i += 2; /* if it's a structured variable, use its type! */ if ( sym->state != SYM_TYPE ) sym = sym->type; sym = SearchNameInStruct( sym, tokenarray[i].string_ptr, &value, 0 ); } while ( sym && tokenarray[i+1].token == T_DOT ); NextIfState = ( sym ? BLOCK_ACTIVE : BLOCK_INACTIVE ); } else { NextIfState = ( check_defd( tokenarray[i].string_ptr ) ? BLOCK_ACTIVE : BLOCK_INACTIVE ); } i++; } else if ( tokenarray[i].token == T_RES_ID && tokenarray[i].tokval == T_FLAT ) { /* v2.09: special treatment of FLAT added */ NextIfState = (( ModuleInfo.flat_grp && ModuleInfo.flat_grp->sym.isdefined ) ? BLOCK_ACTIVE : BLOCK_INACTIVE ); i++; } else if ( Options.strict_masm_compat == FALSE && ( tokenarray[i].token == T_RES_ID || tokenarray[i].token == T_STYPE || tokenarray[i].token == T_INSTRUCTION || tokenarray[i].token == T_DIRECTIVE || //tokenarray[i].token == T_UNARY_OP || //tokenarray[i].token == T_BINARY_OP || tokenarray[i].token == T_REG ) && tokenarray[i+1].token == T_FINAL ) { NextIfState = BLOCK_ACTIVE; i++; } if ( tokenarray[i].token != T_FINAL ) { EmitWarn( 2, IFDEF_EXPECTS_SYMBOL_ARGUMENT, tokenarray[i-1].tokpos ); while ( tokenarray[i].token != T_FINAL ) i++; } if ( directive == T_IFNDEF || directive == T_ELSEIFNDEF ) NextIfState = ( ( NextIfState == BLOCK_ACTIVE ) ? BLOCK_INACTIVE : BLOCK_ACTIVE ); break; default: /* ELSE and ENDIF */ NextIfState = BLOCK_ACTIVE; break; } if ( tokenarray[i].token != T_FINAL ) { return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i].string_ptr ) ); } CurrIfState = NextIfState; DebugMsg1(("CondAsmDirective(%s) exit, state=%s, lvl=%u, falselvl=%u\n", GetResWName(directive, NULL), GetCurrIfStatString(), blocknestlevel, falseblocknestlevel)); return( NOT_ERROR ); } static char * GetErrText( int i, struct asm_tok tokenarray[] ) /************************************************************/ { *StringBufferEnd = NULLC; if ( i ) { if ( tokenarray[i].token != T_STRING || tokenarray[i].string_delim != '<' ) { TextItemError( &tokenarray[i] ); } else { *(StringBufferEnd+0) = ':'; *(StringBufferEnd+1) = ' '; //GetLiteralValue( buffer+2, text->string_ptr ); strcpy( StringBufferEnd+2, tokenarray[i].string_ptr ); } } return( StringBufferEnd ); } /* v2.05: the error directives are no longer handled in the * preprocessor, because the errors are displayed in pass 2 only * - .err [] * - .err<1|2> [] * - .err expression [, ] * - .errdif[i] literal1, literal2 [, ] * - .erridn[i] literal1, literal2 [, ] * - .err[n]b text_literal [, ] * - .err[n]def symbol [, ] */ ret_code ErrorDirective( int i, struct asm_tok tokenarray[] ) /***********************************************************/ { struct expr opndx; unsigned direct; const char *string1; const char *string2; int errmsg = EMPTY; int erridx = 0; struct asym *sym; int idloc; direct = tokenarray[i].tokval; i++; /* go past directive */ /* get an expression if necessary */ switch( GetSflagsSp( direct) ) { case CC_NUMARG: /* .ERR[E|NZ] */ if (( ERROR == EvalOperand( &i, tokenarray, Token_Count, &opndx, 0 ) )) return( ERROR ); if ( opndx.kind == EXPR_CONST ) ; else if ( opndx.kind == EXPR_ADDR && opndx.indirect == FALSE && opndx.sym && opndx.sym->state == SYM_UNDEFINED ) ;//opndx.value += opndx.sym->offset; else { return( EmitError( CONSTANT_EXPECTED ) ); } if ( tokenarray[i].token == T_COMMA && tokenarray[i+1].token != T_FINAL ) { i++; erridx = i++; } if ( Parse_Pass == PASS_1 ) break; if ( direct == T_DOT_ERRNZ && opndx.value ) { errmsg = FORCED_NOT_ZERO; } else if ( direct == T_DOT_ERRE && !opndx.value ) { errmsg = FORCED_EQUAL; } if ( errmsg != EMPTY ) EmitErr( errmsg, opndx.value, GetErrText( erridx, tokenarray ) ); break; case CC_SYMARG: /* .ERR[N]DEF */ /* there's a special handling of these directives in ExpandLine()! */ if ( tokenarray[i].token != T_ID ) { return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i].string_ptr ) ); } idloc = i; /* skip the next param */ do { i++; } while ( tokenarray[i].token == T_DOT || tokenarray[i].token == T_ID ); if ( tokenarray[i].token == T_COMMA && tokenarray[i+1].token != T_FINAL ) { /* v2.05: added */ /* v2.08: obsolete, the expansion occurs in the preprocessor. * See ExpandLine() in expans.c */ //ExpandLineItems( tokenarray[i].tokpos, i, tokenarray, TRUE, FALSE ); i++; erridx = i++; /* Masm seems to accept anything as text */ } //if ( tokenarray[i].token != T_FINAL ) // break; /* should run on pass 2 only! */ if ( Parse_Pass == PASS_1 ) break; /* don't use check_defd()! */ /* v2.07: check for structured variables */ if ( Options.strict_masm_compat == FALSE && tokenarray[idloc+1].token == T_DOT && ( sym = SymSearch( tokenarray[idloc].string_ptr ) ) && ( ( sym->state == SYM_TYPE ) || sym->type ) ) { uint_32 value; int j = idloc; int size; value = 0; do { j += 2; /* if it's a structured variable, use its type! */ if ( sym->state != SYM_TYPE ) sym = sym->type; sym = SearchNameInStruct( sym, tokenarray[j].string_ptr, &value, 0 ); } while ( sym && tokenarray[j+1].token == T_DOT ); if ( tokenarray[j].token == T_ID ) j++; else if ( tokenarray[j].token != T_FINAL && tokenarray[j].token != T_COMMA ) { return( EmitErr( SYNTAX_ERROR_EX, tokenarray[j].string_ptr ) ); } size = tokenarray[j].tokpos - tokenarray[idloc].tokpos; memcpy( StringBufferEnd, tokenarray[idloc].tokpos, size ); *(StringBufferEnd+size) = NULLC; } else { sym = SymSearch( tokenarray[idloc].string_ptr ); strcpy( StringBufferEnd, tokenarray[idloc].string_ptr ); } if ( sym && sym->state == SYM_UNDEFINED ) sym = NULL; /* Masm "usually" ignores the optional errtxt! */ if( erridx ) { strcat( StringBufferEnd, " : " ); strcat( StringBufferEnd, tokenarray[erridx].string_ptr ); } if( direct == T_DOT_ERRDEF && sym != NULL ) EmitErr( FORCED_DEF, StringBufferEnd ); else if( direct == T_DOT_ERRNDEF && sym == NULL ) EmitErr( FORCED_NOT_DEF, StringBufferEnd ); break; case CC_BLKARG: /* .ERR[N]B */ string1 = tokenarray[i].string_ptr; if ( tokenarray[i].token != T_STRING || tokenarray[i].string_delim != '<' ) { return( TextItemError( &tokenarray[i] ) ); } i++; if ( tokenarray[i].token == T_COMMA && tokenarray[i+1].token != T_FINAL ) { i++; erridx = i++; } if ( Parse_Pass == PASS_1 ) break; if ( direct == T_DOT_ERRB && check_blank( string1 ) ) errmsg = FORCED_BLANK; else if ( direct == T_DOT_ERRNB && !check_blank( string1 ) ) errmsg = FORCED_NOT_BLANK; if ( errmsg != EMPTY ) EmitErr( errmsg, string1, GetErrText( erridx, tokenarray ) ); break; case CC_LITARG: /* .ERRDIF[I], .ERRIDN[I] */ string1 = tokenarray[i].string_ptr; if ( tokenarray[i].token != T_STRING || tokenarray[i].string_delim != '<' ) { return( TextItemError( &tokenarray[i] ) ); } i++; if ( tokenarray[i].token != T_COMMA ) { return( EmitErr( EXPECTING_COMMA, tokenarray[i].tokpos ) ); } i++; string2 = tokenarray[i].string_ptr; if ( tokenarray[i].token != T_STRING || tokenarray[i].string_delim != '<' ) { return( TextItemError( &tokenarray[i] ) ); } i++; if ( tokenarray[i].token == T_COMMA && tokenarray[i+1].token != T_FINAL ) { i++; erridx = i++; } if ( Parse_Pass == PASS_1 ) break; switch ( direct ) { case T_DOT_ERRDIF: if ( check_dif( string1, string2, TRUE ) ) errmsg = FORCED_DIF; break; case T_DOT_ERRDIFI: if ( check_dif( string1, string2, FALSE ) ) errmsg = FORCED_DIF; break; case T_DOT_ERRIDN: if ( !check_dif( string1, string2, TRUE ) ) errmsg = FORCED_IDN; break; default: if ( !check_dif( string1, string2, FALSE ) ) errmsg = FORCED_IDN; } if ( errmsg != EMPTY ) EmitErr( errmsg, string1, string2, GetErrText( erridx, tokenarray ) ); break; case CC_PASS2: /* .ERR2 */ if ( ModuleInfo.setif2 == FALSE ) { return( EmitError( IF2_NOT_ALLOWED ) ); } case CC_PASS1: /* .ERR1 */ default: /* .ERR */ if ( tokenarray[i].token != T_FINAL ) { erridx = i++; } if ( Parse_Pass == PASS_1 ) break; EmitErr( FORCED_ERR, GetErrText( erridx, tokenarray ) ); break; } if ( tokenarray[i].token != T_FINAL ) { return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i].tokpos ) ); } return( NOT_ERROR ); } void CondCheckOpen( void ) /************************/ { if( blocknestlevel > 0 ) { EmitErr( BLOCK_NESTING_ERROR, "if-else" ); } return; } int GetIfNestLevel( void ) /************************/ { return( blocknestlevel ); } void SetIfNestLevel( int newlevel ) /*********************************/ { blocknestlevel = newlevel; return; } /* init (called once per module) */ void CondInit( void ) /*******************/ { CurrIfState = BLOCK_ACTIVE; blocknestlevel = 0; falseblocknestlevel = 0; }