/****************************************************************************
*
*                            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:  WHEN YOU FIGURE OUT WHAT THIS FILE DOES, PLEASE
*               DESCRIBE IT HERE!
*
****************************************************************************/


//
// FMACROS      : macro processor
//

#include "ftnstd.h"
#include "global.h"
#include "progsw.h"
#include "errcod.h"
#if _OPT_CG == _ON
#include "cpopt.h"
#endif

#include <string.h>

typedef struct macro_entry {
    struct macro_entry  *link;
    byte                name_len;
    byte                status;
    char                name[1];
} macro_entry;

extern  void            *FMemAlloc(uint);
extern  void            FMemFree(void *);
extern  void            InfoError(uint,...);

static  macro_entry     *MacroList;
static  unsigned char   NestingLevel;
static  unsigned_16     NestingFlags;
static  unsigned_16     NestingStack;
static  unsigned_8      MacroFlags;

#define MAX_NESTING     16
#define MACRO_PERMANENT 0x01

#define DEBUG_MACRO_LEN 9
static  char            DebugMacro[] = { "__debug__" };


void    InitMacroProcessor() {
//============================

// Initialize macro processor in case macros defined on command line.

    MacroFlags = MACRO_PERMANENT;
}


void    FiniMacroProcessor() {
//============================

// Finalize macro processor.

    FreeMacros( TRUE );
}


void    InitPredefinedMacros() {
//==============================

#if _TARGET == _80386
    MacroDEFINE( "__386__", 7 );
#elif _TARGET == _8086
    MacroDEFINE( "__i86__", 7 );
#elif _TARGET == _AXP
    MacroDEFINE( "__AXP__", 7 );
#elif _TARGET == _PPC
    MacroDEFINE( "__PPC__", 7 );
#else
    #error Unknown platform/OS
#endif
#if _OPT_CG == _ON
  #if _TARGET == _80386
    if( CGOpts & CGOPT_STK_ARGS ) {
        MacroDEFINE( "__stack_conventions__", 21 );
    }
  #endif
  #if _TARGET == _8086  || _TARGET == _80386
    if( CPUOpts & (CPUOPT_FPR | CPUOPT_FPI | CPUOPT_FPI87 |
                   CPUOPT_FP287 | CPUOPT_FP387 | CPUOPT_FP5) ) {
        MacroDEFINE( "__FPI__", 7 );
    }
  #endif
#elif _8087 == _ON
    MacroDEFINE( "__FPI__", 7 );
#endif
    MacroFlags = 0;
}


void    InitMacros() {
//====================

// Initialize macro processor for a compilation.

    NestingLevel = 0;
    NestingFlags = 0;
    NestingStack = 0;
}


void    FiniMacros() {
//====================

// Finalize macro processor for a compilation.

    if( NestingLevel != 0 ) {
        InfoError( CO_MACRO_STRUCTURE_MISMATCH );
    }
    FreeMacros( FALSE );
}


static  void    FreeMacros( bool free_perm ) {
//============================================

// Free macros.

    macro_entry *link;

    while( MacroList != NULL ) {
        link = MacroList->link;
        if( !free_perm && ( MacroList->status == MACRO_PERMANENT ) ) break;
        FMemFree( MacroList );
        MacroList = link;
    }
}


static  macro_entry     *FindMacroEntry( char *macro, uint macro_len ) {
//======================================================================

// Find a macro.

    macro_entry *me;

    for( me = MacroList; me != NULL; me = me->link ) {
        if( me->name_len != macro_len ) continue;
        if( memicmp( me->name, macro, macro_len ) == 0 ) return( me );
    }
    return( NULL );
}


bool    CompileDebugStmts() {
//===========================

// Determine if debug statements should be compiled.

    return( FindMacroEntry( DebugMacro, DEBUG_MACRO_LEN ) != NULL );
}


void    MacroDEFINE( char *macro, uint macro_len ) {
//==================================================

// Define a macro.

    macro_entry         *me;

    me = FindMacroEntry( macro, macro_len );
    if( me == NULL ) {
        me = FMemAlloc( sizeof( macro_entry ) + macro_len - sizeof( char ) );
        me->link = MacroList;
        me->name_len = macro_len;
        me->status = MacroFlags;
        memcpy( &me->name, macro, macro_len );
        MacroList = me;
    }
}


void    MacroUNDEFINE( char *macro, uint macro_len ) {
//====================================================

// Define a macro.

    macro_entry         **me;
    macro_entry         *free_me;

    for( me = &MacroList; *me != NULL; me = &(*me)->link ) {
        if( (*me)->name_len != macro_len ) continue;
        if( memicmp( (*me)->name, macro, macro_len ) == 0 ) {
            free_me = *me;
            *me = free_me->link;
            FMemFree( free_me );
            return;
        }
    }
}


static  void    SetSkipStatus() {
//===============================

// Determine whether source is to be skipped.

    int         i;

    for( i = 0; i <= NestingLevel; ++i ) {
        if( NestingFlags & ( 1 << i ) ) {
            ProgSw |= PS_SKIP_SOURCE;
            return;
        }
    }
    ProgSw &= ~PS_SKIP_SOURCE;
}


void    MacroIFDEF( char *macro, uint macro_len ) {
//=================================================

// Process IFDEF directive.

    IFCondition( FindMacroEntry( macro, macro_len ) != NULL );
}


void    MacroIFNDEF( char *macro, uint macro_len ) {
//==================================================

// Process IFNDEF directive.

    IFCondition( FindMacroEntry( macro, macro_len ) == NULL );
}


static  void    IFCondition( bool cond ) {
//========================================

// Process a IF condition.

    if( NestingLevel == MAX_NESTING ) {
        InfoError( CO_MACRO_NESTING_EXCEEDED );
    } else {
        MacroCondition( cond );
        ++NestingLevel;
    }
}


static  void    MacroCondition( bool cond ) {
//===========================================

// Process a macro condition.

    NestingStack |= 1 << NestingLevel;
    if( cond ) {
        NestingFlags &= ~( 1 << NestingLevel );
    } else {
        NestingFlags |= 1 << NestingLevel;
    }
    SetSkipStatus();
}


void    MacroELIFDEF( char *macro, uint macro_len ) {
//===================================================

// Process ELIFDEF directive.

    --NestingLevel;
    if( !( NestingStack & ( 1 << NestingLevel ) ) ) {
        InfoError( CO_MACRO_STRUCTURE_MISMATCH );
    } else {
        if( NestingFlags & ( 1 << NestingLevel ) ) {
            MacroCondition( FindMacroEntry( macro, macro_len ) != NULL );
        } else {
            NestingFlags |= 1 << NestingLevel;
            SetSkipStatus();
            NestingFlags &= ~( 1 << NestingLevel );
        }
    }
    ++NestingLevel;
}


void    MacroELIFNDEF( char *macro, uint macro_len ) {
//====================================================

// Process ELIFNDEF directive.

    --NestingLevel;
    if( !( NestingStack & ( 1 << NestingLevel ) ) ) {
        InfoError( CO_MACRO_STRUCTURE_MISMATCH );
    } else {
        if( NestingFlags & ( 1 << NestingLevel ) ) {
            MacroCondition( FindMacroEntry( macro, macro_len ) == NULL );
        } else {
            NestingFlags |= 1 << NestingLevel;
            SetSkipStatus();
        }
    }
    ++NestingLevel;
}


void    MacroELSE() {
//===================

// Process ELSE directive.

    --NestingLevel;
    if( !( NestingStack & ( 1 << NestingLevel ) ) ) {
        InfoError( CO_MACRO_STRUCTURE_MISMATCH );
    } else {
        NestingStack &= ~( 1 << NestingLevel );
        if( NestingFlags & ( 1 << NestingLevel ) ) {
            NestingFlags &= ~( 1 << NestingLevel );
        } else {
            NestingFlags |= 1 << NestingLevel;
        }
        SetSkipStatus();
    }
    ++NestingLevel;
}


void    MacroENDIF() {
//====================

// Process ENDIF directive.

    if( NestingLevel == 0 ) {
        InfoError( CO_MACRO_STRUCTURE_MISMATCH );
    } else {
        --NestingLevel;
        NestingFlags &= ~( 1 << NestingLevel );
        SetSkipStatus();
    }
}
