From 3d79319c046efd26801d494780d8075fa8d8d92c Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Tue, 26 Sep 2006 01:41:40 +0000 Subject: [PATCH] Register Lua enum types as strings, not numbers. - Even though these strings are in the context of the table, we still prefix them with the enum name ("UnlockRequirement_ArcadePoints", not "ArcadePoints"). The typical use is as a simple string (obj:Call("Type")), not as an index into the table, and this makes the type clear and easy to search for in that case. - no "NUM_UnlockRequirement"; use #UnlockRequirement. - no UnlockRequirement_Invalid; use nil - Enum::Check always returns a valid X; no additional error checking is needed and Lua code can no longer introduce invalid enum values into C - This does not support enum types with holes; neither does FOREACH. - This is typesafe. If you give "GAME_CONTROLLER_1" to a function expecting an UnlockRequirement, we can detect the error and show a meaningful error message. - Lua strings are always merged, and comparisons are as cheap as integers, so this isn't slow (and may be faster). - Can iterate: for i,j in ipairs(UnlockEntry) do func(j) end (This is a bit clumsy; probably want a foreach() iterator that doesn't return i) - ThemeMetric just works. --- stepmania/src/EnumHelper.cpp | 30 ++++++++++++++++ stepmania/src/EnumHelper.h | 66 ++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/stepmania/src/EnumHelper.cpp b/stepmania/src/EnumHelper.cpp index 8bafc1c4c1..c797678725 100644 --- a/stepmania/src/EnumHelper.cpp +++ b/stepmania/src/EnumHelper.cpp @@ -1,5 +1,35 @@ #include "global.h" #include "EnumHelper.h" +#include "LuaManager.h" +#include "RageUtil.h" + +int CheckEnum( lua_State *L, LuaReference &table, int iPos, int iInvalid, const char *szType ) +{ + if( lua_isnil(L, iPos) ) + return iInvalid; + + iPos = LuaHelpers::AbsIndex( L, iPos ); + + table.PushSelf( L ); + lua_pushvalue( L, iPos ); + lua_gettable( L, -2 ); + + // If the result is nil, then a string was passed that is not a member of this enum. Throw + // an error. To specify the invalid value, pass nil. That way, typos will throw an error, + // and not silently result in nil, or an out-of-bounds value. + if( unlikely(lua_isnil(L, -1)) ) + { + // XXX: show string if a string, otherwise the type + lua_pushvalue( L, iPos ); + RString sGot; + LuaHelpers::Pop( L, sGot ); + LuaHelpers::Push( ssprintf("Expected %s; got \"%s\"", szType, sGot.c_str() ), L ); + lua_error( L ); + } + int iRet = lua_tointeger( L, -1 ); + lua_pop( L, 2 ); + return iRet; +} /* * (c) 2004 Chris Danford diff --git a/stepmania/src/EnumHelper.h b/stepmania/src/EnumHelper.h index 2e6b4c5f2b..71d51cab94 100644 --- a/stepmania/src/EnumHelper.h +++ b/stepmania/src/EnumHelper.h @@ -1,6 +1,8 @@ #ifndef ENUM_HELPER_H #define ENUM_HELPER_H +#include "LuaReference.h" + /* * Safely add an integer to an enum. * @@ -42,6 +44,34 @@ static inline T enum_add2( T val, int iAmt ) #define FOREACH_ENUM( e, max, var ) for( e var=(e)0; var( var, +1 ) ) #define FOREACH_ENUM2( e, var ) for( e var=(e)0; var( var, +1 ) ) +int CheckEnum( lua_State *L, LuaReference &table, int iPos, int iInvalid, const char *szType ); + +template +struct Enum +{ + static LuaReference StringToEnum; + static LuaReference EnumToString; + static T Invalid; + static const char *szName; + static T Check( lua_State *L, int iPos ) + { + return (T) CheckEnum( L, StringToEnum, iPos, Invalid, szName ); + } + static void Push( lua_State *L, T iVal ) + { + /* Enum_Invalid values are nil in Lua. */ + if( iVal == Invalid ) + { + lua_pushnil( L ); + return; + } + + /* Look up the string value. */ + EnumToString.PushSelf( L ); + lua_rawgeti( L, -1, iVal + 1 ); + lua_replace( L, -2 ); + } +}; static const RString EMPTY_STRING; @@ -124,6 +154,42 @@ static void Lua##X(lua_State* L) \ } \ REGISTER_WITH_LUA_FUNCTION( Lua##X ); +#define LuaDeclareType(X) \ +namespace LuaHelpers { bool FromStack( lua_State *L, X &Object, int iOffset ); } + +#define LuaXType2(X, CNT, Prefix) \ +static void Lua2##X(lua_State* L) \ +{ \ + Enum::Invalid = enum_add2( CNT, 1 ); \ + /* Create the public EnumToString table: { "UnlockEntry_ArcadePoints", "UnlockEntry_DancePoints" } */ \ + lua_newtable( L ); \ + FOREACH_ENUM( X, CNT, i ) \ + { \ + RString s = X##Names[i]; \ + lua_pushstring( L, Prefix+s ); \ + lua_rawseti( L, -2, i+1 ); /* 1-based */ \ + } \ + Enum::EnumToString.SetFromStack( L ); \ + Enum::EnumToString.PushSelf( L ); \ + lua_setglobal( L, #X ); \ + /* Create the private StringToEnum table: { "UnlockEntry_ArcadePoints" = 0, "UnlockEntry_DancePoints" = 0 } */ \ + lua_newtable( L ); \ + FOREACH_ENUM( X, CNT, i ) \ + { \ + RString s = X##Names[i]; \ + lua_pushstring( L, Prefix+s ); \ + lua_pushnumber( L, i ); /* 0-based */ \ + lua_rawset( L, -3 ); \ + } \ + Enum::StringToEnum.SetFromStack( L ); \ +} \ +REGISTER_WITH_LUA_FUNCTION( Lua2##X ); \ +LuaReference Enum::StringToEnum; \ +LuaReference Enum::EnumToString; \ +X Enum::Invalid; \ +const char *Enum::szName = #X; \ +namespace LuaHelpers { bool FromStack( lua_State *L, X &Object, int iOffset ) { Object = Enum::Check( L, iOffset ); return true; } } + #endif /*