lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]



I got the enum patch working, that's good news.

The bad news is, it's actually slower than my userdata-based approach (outside of lua core) used to be..

So. I'd very much like someone to read the code & comment. The intention is to provide 'class aware' unsigned 32-bit entities that greatly help making wrappers for C functions using bitfields, or other 'magic values'.

By making this a core patch, I was trying to both optimize the performance (no mallocs needed) and offer the feature to any Lua users (already in LuaX).

Here's the main source file to get you started, for a full Lua 5.1w4 based tarball, please ask.

-ak

/*
** $Id: lenum.c,v ... $
** Metamethods for enum types
** See Copyright Notice in lua.h
*/

#ifdef ENUM_PATCH

#define lenum_c
#define LUA_CORE

#include "lobject.h"
#include "lstate.h"     // registry()
#include "ltable.h"     // luaH_getnum()
#include "lenum.h"

#include <string.h>
#include <ctype.h>      // tolower()


/*--- Local functions -----------------------------------------*/

// Shift 'val' down to where 'mask' begins
//
static 
lua_Enum Loc_MaskDown( lua_Enum val, lua_Enum mask )
{
    if (mask==0) return 0;  // hmm.. no set bits, weird

    while( (mask&0x01) == 0 )   // shift both mask & value down to 0..N
        { mask >>= 1; val >>= 1; }
    
    return val;
}


/*--- Enumeration methods -----------------------------------------*/

//---
// bool= call( enum a, ["test",] enum b, ... )
// enum= call( enum a, "or"|"and", enum b, ... )
// enum= call( enum a, "xor", [enum b, ...] )
// enum= call( enum a, "<<"|">>" [,int=1] )
// num= call( enum a, "number" )
// str= call( enum a, "string" [,base_uint=10 [,width_uint]] )
//
// Multitude of binary operations can be implemented via the 'call' metamethod.

// All operators have different first characters, this speeds us up.
#define OP_TEST 't'
#define OP_OR   'o'
#define OP_AND  'a'
#define OP_XOR  'x'
#define OP_SHL  '<'
#define OP_SHR  '>'
#define OP_NUM  'n'
#define OP_STR  's'

static
int enum_call( lua_State *L )
{
const char* op= NULL;
char op_c= '\0';
unsigned n=1;
int argn= lua_gettop(L);

    lua_EnumTag tag= lua_enumtag(L,1);
    lua_Enum a= lua_toenum(L,1,tag);

    if (lua_isstring(L,2))
        {
        op= lua_tostring(L,2);
        op_c= tolower(*op);
        n++;
        }

    if ((!op) || (op_c==OP_TEST))    // the only returning 'bool'
        {
        lua_Enum mask=0;
        while( ++n <= argn )
            mask |= lua_toenum(L,n,tag);    // must have same tags

        lua_pushboolean( L, a & mask );
        return 1;   // done! :)
        }

    if (op_c==OP_NUM)
        {
        lua_pushinteger( L, a );
        return 1;
        }

    if (op_c==OP_STR)
        {
        int base= lua_tointeger(L,3);  // 0 == none
        int width= lua_tointeger(L,4);
        char buf[32+1];   // long enough
        
        switch( base )
            {
            case 2: {   // no 'sprintf()' for binary
                char* ptr= buf;
                if (!width) width=32;
                while( width-- )
                    *ptr++= (a & (1<<width)) ? '1':'0';
                *ptr= '\0';
                } break;
            default:    
            case 10:
                sprintf( buf, "%lu", a );    // width ignored
                break;
            case 16:
                sprintf( buf, "%0*lx", width ? width:8, a );
                break;
            }

        lua_pushstring(L,buf);
        return 1;
        }
        
    switch( op_c )  // these all return 'enum'
        {
        case OP_OR:
            while( ++n <= argn )
                a |= lua_toenum(L,n,tag);
            break;

        case OP_AND:
            while( ++n <= argn )
                a &= lua_toenum(L,n,tag);
            break;

        case OP_XOR:
            if (argn==2)
                a ^= 0xffffffffU;
            else
                while( ++n <= argn )
                    a ^= lua_toenum(L,n,tag);
            break;

        case OP_SHL:  // <<
            { int shift= lua_tointeger(L,3);
            a <<= (shift ? shift:1); }
            break;
            
        case OP_SHR:  // >>
            // Note: We're _not_ using arithmetic shift here, although the values
            //       are otherwise regarded as signed. Bit31 always becomes zero.
            //       
            { int shift= lua_tointeger(L,3);
            a= ((unsigned long)a) >> (shift ? shift:1); }
            break;
            
        default:
            lua_pushfstring( L, "Unknown operator: %s", op );
            lua_error( L );
        }

    lua_pushenum( L, a, tag );
    return 1;
}


//---
// int= index( enum a, enum key )
// int= index( enum a, uint bit )
//
// Index notation can be used for extracting a certain flag bit or a masked value
// within the enum. I.e. "var[FLAG]".  Note that unlike the "var(FLAG)" syntax,
// which returns a boolean, we return the actual numeric value, scaled to 0..N.
//
// Use '()' in if/else testing, and [] if actual values are wanted.
//
static
int enum_index( lua_State *L )
{
unsigned long mask;  // must be unsigned, so shift down places 0's at MSB.
int ret=0;

    lua_EnumTag tag= lua_enumtag(L,1);
    lua_Enum a= lua_toenum(L,1,tag);

    if (lua_enumtag(L,2))
        mask= lua_toenum(L,2,tag);     // gives error if wrong family
    else
    if (lua_isinteger(L,2))
        mask= 1L << lua_tointeger(L,2);
    else
        { lua_pushfstring( L, "Bad index: %s", lua_typename(L,2) );
        lua_error( L ); mask=0; }

    ret= Loc_MaskDown( a & mask, mask );
    
    lua_pushinteger( L, ret );
    return 1;
}

//---
// void= newindex( enum a, enum/uint key, enum/uint/bool value )
//
// Setting bits by the table notation:
//
//      myflags[FULLSCREEN]= true
//      myflags[BITFIELD]= 10
//      myflags[BITFIELD]= PRESET_VALUE
//      myflags[20]= 1
//
static
int enum_newindex( lua_State *L )
{
unsigned long mask;
lua_Enum val;
int /*bool*/ shift_up= 1;

    lua_EnumTag tag= lua_enumtag(L,1);
    lua_Enum a= lua_toenum(L,1,tag);

    if (lua_enumtag(L,2))
        mask= lua_toenum(L,2,tag);     // gives error if wrong family
    else
    if (lua_isinteger(L,2))
        mask= 1L << lua_tointeger(L,2);
    else
        { lua_pushfstring( L, "Bad index: %s", lua_typename(L,2) );
        lua_error( L ); mask=0; }

    //---
    if (lua_enumtag(L,3))
        {
        val= lua_toenum(L,3,tag);     // gives error if wrong family
        shift_up= 0;    // already at right positions
        }
    else
    if (lua_isinteger(L,3))
        val= lua_tointeger(L,2);
    else
    if (lua_isnil(L,3) || lua_isboolean(L,3))
        {
        // 'true' sets all the bits of a bitfield (if mask is wider than 1)
        //
        val= lua_toboolean(L,3) ? (lua_Enum)(-1) : 0;
        }
    else
        { lua_pushfstring( L, "Bad value: %s", lua_typename(L,3) );
        lua_error( L ); val=0; }

    //---
    if (shift_up && (val!=0))   // shift according to 'mask'
        {
        unsigned long n= mask;
        while( (n&0x01) == 0 )
            { n >>= 1; val <<= 1; }
        }

    a &= ~mask;         // clear masked bits
    a |= val & mask;    // set new ones

    // In-place modification of the enum value (not public Lua API, but we can.. :)
    //
    // TBD: Or, can we?  Is the value on the stack [1] the real one, or just a copy?
    //
    modevalue( L, 1, a, tag );
    
    return 0;   // void
}


/*--- Enumeration management -----------------------------------------*/

// Some helper macros (from 'gluax.h'):
//
#include <assert.h>
#define /*int abs_index*/ STACK_ABS( L, index ) \
	( (index) >= 0 ? (index) /*absolute*/ : (lua_gettop(L) +(index) +1) )

#define STACK_CHECK(L)     { lua_State* _luastack_= (L); \
                             int _oldtop_= lua_gettop(L);
#define STACK_END(change)  assert( lua_gettop(_luastack_) == _oldtop_+(change) ); }

// Key names used for registry:
//
#define _ENUM       "_ENUM"

//-----
// Initialize the enum storage system
//
void enum_open( lua_State *L )
{
  STACK_CHECK(L) {
    // create class name lookup
    lua_newtable(L);
    lua_setfield(L, LUA_REGISTRYINDEX, _ENUM);

    // Place method closures directly in the registry (as fast access as possible)
    //
    // WARN: This is rather nasty, we use the registry's '__call', '__index' and
    //       '__newindex' names, because that's just easier to do.. (see 'enum_gettm').
    //       Should fix this some day to be more polite. :)
    //
    lua_pushliteral( L, "__call" );   // read by 'G(L)->tmname[TM_CALL]'
    lua_pushcfunction( L, enum_call );
    lua_rawset(L,LUA_REGISTRYINDEX);    // eats both index & function

    lua_pushliteral( L, "__index" );
    lua_pushcfunction( L, enum_index );
    lua_rawset(L,LUA_REGISTRYINDEX);

    lua_pushliteral( L, "__newindex" );
    lua_pushcfunction( L, enum_newindex );
    lua_rawset(L,LUA_REGISTRYINDEX);
    } 
  STACK_END(0);     // should be no change
}

//-----
// Return enum metamethod-like closure
//
const TValue *enum_gettm( lua_State *L, TMS ev )   // TM_CALL, TM_INDEX, TM_NEWINDEX
{
    // WARNING: Not sure if this works.. (we shouldn't touch the stack if possible)
    //
    StkId o= registry(L);    assert(o);
    Table* t= hvalue(o);     assert(t);
    
    const TValue* ret= luaH_getstr( t, G(L)->tmname[ev] );
    
fprintf( stderr, "enum metamethod: %s -> %p\n", 
                    (ev==TM_CALL) ? "TM_CALL" :
                    (ev==TM_INDEX) ? "TM_INDEX" :
                    (ev==TM_NEWINDEX) ? "TM_NEWINDEX" : "", ret );
    assert(ret);
    assert( iscfunction(ret) );
    
    return ret;     // Huh!
}

//-----
// Marry enum 'tt' value and it's id string together. (no divorces allowed!)
// The caller should have checked (should know) that the 'tt' value is free.
//
void enum_marry( lua_State *L, int tt, const char *id )
{
  STACK_CHECK(L) {
    lua_pushliteral( L, _ENUM );
    lua_rawget(L,LUA_REGISTRYINDEX);
    // [-1]= enum lookup table
    
fprintf( stderr, "Marrying %s to %d\n", id, tt );

    lua_pushstring( L, id );    // key
    lua_pushinteger( L, tt );   // value
    lua_rawset(L,-3);           // in the lookup (eats key&val)
    
    lua_pop(L,1);   // remove lookup
    }
  STACK_END(0);   // no change
}

//-----
// Return enum family name
//
const char *enum_name_by_tag( lua_State *L, int tt )
{
const char *ret= NULL;

  STACK_CHECK(L) {
    lua_pushliteral( L, _ENUM );
    lua_rawget(L,LUA_REGISTRYINDEX);
    // [-1]= lookup

    lua_pushnil(L);     // first key (tbl now [-2])
    while (lua_next(L, -2) != 0) {
        // [-3]= lookup
        // [-2]= key (string)
        // [-1]= value (integer)
        //
        if (lua_tointeger(L,-1) == tt) {  // match?
            ret= lua_tostring(L,-2);    // we know it's a string
            lua_pop(L,2);   // removes both value,key (but pointer remains valid until back in Lua)
            break;
            }
        lua_pop(L, 1);  // removes value, keeps key
        }
    // [-1]= lookup
    lua_pop(L,1);
    }
  STACK_END(0);

fprintf( stderr, "enum_name_by_tag: %d -> %s\n", tt, ret?ret:"NULL" );
    
    return ret;
}

//-----
// Return tag if already registered
//
int enum_tag_by_name( lua_State *L, const char *id )
{
int tt= 0;

  STACK_CHECK(L) {
    lua_pushliteral( L, _ENUM );
    lua_rawget(L,LUA_REGISTRYINDEX);
    // [-1]= lookup
    
    lua_pushstring( L, id );    // key
    lua_rawget(L,-2);
    // [-2]= lookup
    // [-1]= value (int) or 'nil'
    
    if (lua_isinteger(L,-1))
        tt= lua_tointeger(L,-1);

    lua_pop(L,2);   // remove pushed value & lookup
    }
  STACK_END(0);

fprintf( stderr, "enum_tag_by_name: %s -> %d\n", id, tt );

    return tt;
}

#endif  // ENUM_PATCH