/****************************************************************************
*
*                            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!
*
****************************************************************************/


#include <stdlib.h>
#include <limits.h>
#include "plusplus.h"
#include "errdefns.h"
#include "stringl.h"
#include "ptree.h"
#include "enum.h"
#include "i64.h"


union _us                   // to enable either signed or unsigned treatment
{   signed_64 s64val;       // - signed/unsigned 64
#if defined( WATCOM_BIG_ENDIAN )
    struct {
        unsigned pad_u;
        unsigned uval;      // - unsigned
    };
    struct {
        unsigned pad_i;
        int sval;           // - signed
    };
#else
    unsigned uval;          // - unsigned
    int sval;               // - signed
#endif
};

struct enum_range           // describe range for enum
{   union _us lo;           // - low value
    union _us hi;           // - high value
    type_id id;             // - corresponding type
    unsigned :0;            // alignment
};

static struct enum_range const range_table[] =
#if _CPU == 386 || _CPU == _AXP
{   {   I64Val( 0xFFFFFFFF, 0xFFFFFF80 )
      , I64Val( 0x00000000, 0x0000007F )
      , TYP_SCHAR
    }
,   {   I64Val( 0x00000000, 0x00000000 )
      , I64Val( 0x00000000, 0x000000FF )
      , TYP_UCHAR
    }
,   {   I64Val( 0xFFFFFFFF, 0xFFFF8000 )
      , I64Val( 0x00000000, 0x00007FFF )
      , TYP_SSHORT
    }
,   {   I64Val( 0x00000000, 0x00000000 )
      , I64Val( 0x00000000, 0x0000FFFF )
      , TYP_USHORT
    }
,   {   I64Val( 0xFFFFFFFF, 0x80000000 )
      , I64Val( 0x00000000, 0x7FFFFFFF )
      , TYP_SINT
    }
,   {   I64Val( 0x00000000, 0x00000000 )
      , I64Val( 0x00000000, 0xFFFFFFFF )
      , TYP_UINT
    }
,   {   I64Val( 0x80000000, 0x00000000 )
      , I64Val( 0x7FFFFFFF, 0xFFFFFFFF )
      , TYP_SLONG64
    }
,   {   I64Val( 0x00000000, 0x00000000 )
      , I64Val( 0xFFFFFFFF, 0xFFFFFFFF )
      , TYP_ULONG64
    }
};
    #define RANGE_INDEX_SINT 4
#elif _CPU == 8086
{   {   I64Val( 0xFFFFFFFF, 0xFFFFFF80 )
      , I64Val( 0x00000000, 0x0000007F )
      , TYP_SCHAR
    }
,   {   I64Val( 0x00000000, 0x00000000 )
      , I64Val( 0x00000000, 0x000000FF )
      , TYP_UCHAR
    }
,   {   I64Val( 0xFFFFFFFF, 0xFFFF8000 )
      , I64Val( 0x00000000, 0x00007FFF )
      , TYP_SINT
    }
,   {   I64Val( 0x00000000, 0x00000000 )
      , I64Val( 0x00000000, 0x0000FFFF )
      , TYP_UINT
    }
,   {   I64Val( 0xFFFFFFFF, 0x80000000 )
      , I64Val( 0x00000000, 0x7FFFFFFF )
      , TYP_SLONG
    }
,   {   I64Val( 0x00000000, 0x00000000 )
      , I64Val( 0x00000000, 0xFFFFFFFF )
      , TYP_ULONG
    }
};
    #define RANGE_INDEX_SINT 2
#else
#error unknown _CPU
#endif

#define ENUM_RNG_MAX ( sizeof(range_table) / sizeof(range_table[0]) )


// Note: During overloading, it is a standard conversion from an enum to
//       the first of int, unsigned int, long, unsigned long which will
//       hold all the values. The underlying type must be either the first
//       of these which holds all the values or must be a type corresponding
//       to the algorithm in FNOVRANK for enum->arith conversions.
//
//       The underlying types are assigned so as to not affect overloading
//       when -ei flag is used or not.
//
static type_id figureOutBaseType( ENUM_DATA *edata )
{
    type_id base_type;
    unsigned index;
    signed_64 const* next_val;

    index = edata->index;
    next_val = &edata->next_value;
    if( edata->has_sign ) {
        for( ; index < ENUM_RNG_MAX ; index += 2 ) {
            if( I64Cmp( next_val, &(range_table[ index ].lo.s64val) ) >= 0
             && I64Cmp( next_val, &(range_table[ index ].hi.s64val) ) <= 0 ) break;
        }
    } else {
        for( ; index < ENUM_RNG_MAX; index += 1 ) {
            if( U64Cmp( next_val, &(range_table[ index ].hi.s64val) ) <= 0 ) break;
        }
    }
    if( index >= ENUM_RNG_MAX ) {
        CErr1( ERR_NO_ENUM_TYPE_POSSIBLE );
        edata->next_value.u._32[ 0 ] = 0;
        edata->next_value.u._32[ 1 ] = 0;
        index = ENUM_RNG_MAX - 1;
    }
    edata->index = (uint_8)index;
    base_type = range_table[ index ].id;
    return base_type;
}

void InitEnumState( ENUM_DATA *edata, PTREE id )
/**********************************************/
{
    char *name;

    edata->type = TypeError;
    if( CompFlags.make_enums_an_int ) {
        edata->base_id = TYP_SINT;
        edata->index = RANGE_INDEX_SINT;
    } else {
        edata->base_id = TYP_SCHAR;
        edata->index = 0;
    }
    edata->next_value.u._32[0] = 0;
    edata->next_value.u._32[1] = 0;
    edata->next_signed = FALSE;
    edata->has_sign = FALSE;
    name = NULL;
    SrcFileGetTokenLocn( &(edata->locn) );
    if( id != NULL ) {
        name = id->u.id.name;
        edata->locn = id->locn;
        PTreeFree( id );
    }
    edata->name = name;
    edata->sym = NULL;
}

void EnumDefine( ENUM_DATA *edata )
/*********************************/
{
    char *enum_typedef_name;
    SYMBOL sym;
    TYPE enum_type;
    TYPE base_type;

    // 7.2 para 5
    // If the  enumerator-list is empty, the underlying type is as if the
    // enumeration had a single enumerator with value 0.
    base_type = GetBasicType( edata->base_id );
    enum_type = MakeType( TYP_ENUM );
    enum_type->of = base_type;
    enum_typedef_name = edata->name;
    if( enum_typedef_name != NULL ) {
        /* we have a named enum type */
        sym = AllocSymbol();
        sym->sym_type = enum_type;
    } else {
        sym = SymMakeDummy( enum_type, &enum_typedef_name );
        enum_type->flag |= TF1_UNNAMED;
    }
    sym->id = SC_TYPEDEF;
    edata->sym = sym;
    SymbolLocnDefine( &(edata->locn), sym );
    sym = InsertSymbol( CurrScope, sym, enum_typedef_name );
    enum_type->u.t.scope = CurrScope;
    enum_type->u.t.sym = sym;
    edata->type = CheckDupType( enum_type );
}


void MakeEnumMember( ENUM_DATA *edata, PTREE id, PTREE val )
/**********************************************************/
{
    SYMBOL sym;
    signed_64 incr;

    /* enter the enumerated constant into the symbol table */
    sym = AllocSymbol();
    sym->id = SC_ENUM;
    sym->sym_type = edata->type;
    SymbolLocnDefine( &(id->locn), sym );
    sym = InsertSymbol( CurrScope, sym, id->u.id.name );
    /* add the value into the symbol */
    if( val != NULL ) {
        /* value was specified */
        DbgVerify( val->op == PT_INT_CONSTANT, "Bad enum value" );
        if( SignedIntType( val->type ) ) {
            edata->next_signed = TRUE;
        } else {
            edata->next_signed = FALSE;
        }
        if( NULL == Integral64Type( val->type ) ) {
            if( edata->next_signed
             && val->u.int_constant >= 0 ) {
                edata->next_signed = FALSE;
            }
        } else {
            if( edata->next_signed
             && ! val->u.int64_constant.u.sign.v ) {
                edata->next_signed = FALSE;
            }
        }
        if( ! edata->has_sign
         && edata->next_signed ) {
            edata->has_sign = TRUE;
            if ( edata->index & 1 ) {
                // move to next bigger signed range
                ++ edata->index;
            }
        }
        edata->next_value = val->u.int64_constant;
        PTreeFree( val );
    }
    PTreeFree( id );
    edata->base_id = figureOutBaseType( edata );
    edata->type->of = GetBasicType( edata->base_id );
    SymBindConstant( sym, edata->next_value );
    if( ! edata->next_signed ) {
        // value is not signed negative (to avoid sign extension later on)
        sym->flag |= SF_ENUM_UINT;
    }
    incr.u._32[ I64LO32 ] = 1;
    incr.u._32[ I64HI32 ] = 0;
    U64Add( &edata->next_value, &incr, &edata->next_value );
    // what about wrap around ? to zero?
}

DECL_SPEC *MakeEnumType( ENUM_DATA *edata )
/*****************************************/
{
    DECL_SPEC *dspec;

    dspec = PTypeActualType( edata->type );
    dspec->type_defined = TRUE;
    dspec->type_declared = TRUE;
    return( dspec );
}

static boolean enumNameOK( TYPE type, char *name )
{
    TYPE enum_type;

    if( type->id == TYP_TYPEDEF ) {
        if( ScopeId( type->u.t.scope ) == SCOPE_TEMPLATE_PARM ) {
            if( type->u.t.sym->name->name == name ) {
                // 14.2.1 para 2
                // <class T> can be ref'd as enum T if an enum type is used
                return( TRUE );
            }
        }
    }
    enum_type = EnumType( type );
    if( SimpleTypeName( enum_type ) != name ) {
        return( FALSE );
    }
    return( TRUE );
}

DECL_SPEC *EnumReference( ENUM_DATA *edata )
/******************************************/
{
    char *name;
    DECL_SPEC *dspec;
    TYPE type;
    TYPE ref_type;
    SEARCH_RESULT *result;
    SYMBOL sym;
    SYMBOL_NAME sym_name;

    ref_type = TypeError;
    name = edata->name;
    if( name != NULL ) {
        result = ScopeFindLexicalEnumType( CurrScope, name );
        if( result != NULL ) {
            sym_name = result->sym_name;
            sym = sym_name->name_type;
            type = sym->sym_type;
            if( ! enumNameOK( type, name ) ) {
                ScopeFreeResult( result );
                result = NULL;
            }
        }
        if( result != NULL ) {
            if( ScopeCheckSymbol( result, sym ) == FALSE ) {
                /* no errors */
                ref_type = type;
            }
            ScopeFreeResult( result );
        } else {
            CErr2p( ERR_UNDECLARED_ENUM_SYM, name );
        }
    } else {
        CErr1( ERR_CANNOT_REFERENCE_UNNAMED_ENUM );
    }
    dspec = PTypeActualType( ref_type );
    dspec->nameless_allowed = TRUE;
    dspec->type_elaborated = TRUE;
    dspec->name = name;
    return( dspec );
}
