diff --git a/stepmania/src/LuaFunctions.h b/stepmania/src/LuaFunctions.h new file mode 100644 index 0000000000..4a50b3dfcd --- /dev/null +++ b/stepmania/src/LuaFunctions.h @@ -0,0 +1,85 @@ +#ifndef LUA_FUNCTIONS_H +#define LUA_FUNCTIONS_H + +#include "LuaHelpers.h" + +extern "C" +{ +#include +#include +} + +/* Argument helpers: */ +#define LUA_ASSERT( expr, err ) if( !(expr) ) { Lua::Fail( L, err ); } +/* require exactly n arguments */ +#define REQ_ARGS(func, need) { \ + const int args = lua_gettop(L); \ + LUA_ASSERT( args == need, ssprintf( func " requires exactly %i argument%s, got %i", need, need == 1? "":"s", args) ); \ +} +/* argument n must be of type "type" */ +#define REQ_ARG(func, n, type) { \ + LUA_ASSERT( lua_is##type(L, n), ssprintf("Argument %i to " func " must be %s", n, #type) ); } +/* argument n must be a number between minimum...maximum */ +#define REQ_ARG_NUMBER_RANGE(func, n, minimum, maximum) { \ + REQ_ARG(func, n, number); \ + const int val = (int) lua_tonumber( L, n ); \ + LUA_ASSERT( val >= minimum && val <= maximum, ssprintf("Argument %i to " func " must be an integer between %i and %i (got %i)", n, minimum, maximum, val) ); \ +} +#define LUA_RETURN( expr ) { Lua::PushStack( L, expr ); return 1; } + +/* Helpers to create common functions: */ +/* Functions that take no arguments: */ +#define LuaFunction_NoArgs( func, call ) \ +int LuaFunc_##func( lua_State *L ) { \ + REQ_ARGS( #func, 0 ); \ + LUA_RETURN( call ); \ +} \ +LuaFunction( func ); /* register it */ + +#define LuaFunction_IntInt( func, call ) \ +int LuaFunc_##func( lua_State *L ) { \ + REQ_ARGS( #func, 2 ); \ + REQ_ARG( #func, 1, number ); \ + REQ_ARG( #func, 2, number ); \ + const int a1 = int(lua_tonumber( L, 1 )); \ + const int a2 = int(lua_tonumber( L, 2 )); \ + LUA_RETURN( call ); \ +} \ +LuaFunction( func ); /* register it */ + +#define LuaFunction_Str( func, call ) \ +int LuaFunc_##func( lua_State *L ) { \ + REQ_ARGS( #func, 1 ); \ + REQ_ARG( #func, 1, string ); \ + CString str; \ + Lua::PopStack( L, str ); \ + LUA_RETURN( call ); \ +} \ +LuaFunction( func ); /* register it */ + +/* Functions that take a single PlayerNumber argument: */ +#define LuaFunction_PlayerNumber( func, call ) \ +int LuaFunc_##func( lua_State *L ) { \ + REQ_ARGS( #func, 1 ); \ + REQ_ARG_NUMBER_RANGE( #func, 1, 1, NUM_PLAYERS ); \ + const PlayerNumber pn = (PlayerNumber) (int(lua_tonumber( L, -1 ))-1); \ + LUA_RETURN( call ); \ +} \ +LuaFunction( func ); /* register it */ + + +/* Linked list of functions we make available to Lua. */ +struct LuaFunctionList +{ + LuaFunctionList( CString name, lua_CFunction func ); + CString name; + lua_CFunction func; + LuaFunctionList *next; +} extern *g_LuaFunctionList; +#define LuaFunction( func ) static LuaFunctionList g_##func( #func, LuaFunc_##func ) + +#endif +/* + * Copyright (c) 2004 by the person(s) listed below. All rights reserved. + * Glenn Maynard + */ diff --git a/stepmania/src/LuaHelpers.cpp b/stepmania/src/LuaHelpers.cpp new file mode 100644 index 0000000000..0ca2656257 --- /dev/null +++ b/stepmania/src/LuaHelpers.cpp @@ -0,0 +1,183 @@ +#include "global.h" +#include "LuaHelpers.h" +#include "LuaFunctions.h" +#include "RageUtil.h" +#include "RageLog.h" + +#include +#include + +LuaFunctionList *g_LuaFunctionList = NULL; + +#if defined(_WINDOWS) + #pragma comment(lib, "lua-5.0/lua.lib") + + /* "interaction between '_setjmp' and C++ object destruction is non-portable" + * We don't care; we'll throw a fatal exception immediately anyway. */ + #pragma warning (disable : 4611) +#endif + + + +struct ChunkReaderData +{ + const CString *buf; + bool done; + ChunkReaderData() { buf = NULL; done = false; } +}; + +const char *ChunkReaderString( lua_State *L, void *ptr, size_t *size ) +{ + ChunkReaderData *data = (ChunkReaderData *) ptr; + if( data->done ) + return NULL; + + data->done = true; + + *size = data->buf->size(); + const char *ret = data->buf->data(); + + return ret; +} + +void Lua::PushStack( lua_State *L, int out ) +{ + /* XXX: stack bounds */ + lua_pushnumber( L, out ); +} + +void Lua::PushStack( lua_State *L, bool out ) +{ + /* XXX: stack bounds */ + lua_pushboolean( L, out ); +} + + +void Lua::PushStack( lua_State *L, void *out ) +{ + if( out ) + lua_pushlightuserdata( L, out ); + else + lua_pushnil( L ); +} + +void Lua::PushStack( lua_State *L, const CString &out ) +{ + lua_pushstring( L, out ); +} + +void Lua::PopStack( lua_State *L, CString &out ) +{ + /* There must be at least one entry on the stack. */ + ASSERT( lua_gettop(L) > 0 ); + ASSERT( lua_isstring(L, -1) ); + + out = lua_tostring( L, -1 ); + lua_pop( L, -1 ); +} + +void LoadFromString( lua_State *L, const CString &str ) +{ + ChunkReaderData data; + data.buf = &str; + int ret = lua_load( L, ChunkReaderString, &data, "in" ); + + if( ret ) + { + CString err; + Lua::PopStack( L, err ); + RageException::Throw( "Error loading script \"%s\": %s", str.c_str(), err.c_str() ); + } +} + + + + + +static jmp_buf jbuf; +static CString jbuf_error; + +int LuaPanic( lua_State *L ) +{ + CString err; + Lua::PopStack( L, err ); + + /* Grr. We can't throw an exception from here: it'll explode going through the + * Lua stack for some reason. Get it off the stack with a longjmp and throw + * an exception from there. */ + jbuf_error = err; + longjmp( jbuf, 1 ); +} + +bool Lua::RunExpression( const CString &str ) +{ +try { + if( setjmp(jbuf) ) + throw jbuf_error; + lua_State *L = lua_open(); + ASSERT( L ); + + lua_atpanic( L, LuaPanic ); + + luaopen_base( L ); + lua_settop(L, 0); // luaopen_* pushes stuff onto the stack that we don't need + + Lua::RegisterFunctions( L ); + + LoadFromString( L, "return " + str ); + RAGE_ASSERT_M( lua_gettop(L) == 1, ssprintf("%i", lua_gettop(L)) ); + + int ret = lua_pcall(L, 0, 1, 0); + if( ret ) + { + CString err; + Lua::PopStack( L, err ); + RageException::Throw( err ); + } + + RAGE_ASSERT_M( lua_gettop(L) == 1, ssprintf("%i", lua_gettop(L)) ); + bool result = !!lua_toboolean( L, -1 ); + lua_pop( L, -1 ); + + lua_close( L ); + + return result; +} catch( const CString &e ) { + RageException::Throw( "Error running \"%s\": %s", str.c_str(), e.c_str() ); +} +} + +void Lua::Fail( lua_State *L, const CString &err ) +{ + lua_pushstring( L, err ); + lua_error( L ); +} + +void Lua::RegisterFunctions( lua_State *L ) +{ + for( const LuaFunctionList *p = g_LuaFunctionList; p; p=p->next ) + lua_register( L, p->name, p->func ); +} + +LuaFunctionList::LuaFunctionList( CString name_, lua_CFunction func_ ) +{ + name = name_; + func = func_; + next = g_LuaFunctionList; + g_LuaFunctionList = this; +} + + +LuaFunction_NoArgs( MonthOfYear, GetLocalTime().tm_mon ); +LuaFunction_NoArgs( DayOfMonth, GetLocalTime().tm_mday ); +LuaFunction_NoArgs( Hour, GetLocalTime().tm_hour ); +LuaFunction_NoArgs( Minute, GetLocalTime().tm_min ); +LuaFunction_NoArgs( Second, GetLocalTime().tm_sec ); +LuaFunction_NoArgs( Year, GetLocalTime().tm_year+1900 ); +LuaFunction_NoArgs( Weekday, GetLocalTime().tm_wday ); +LuaFunction_NoArgs( DayOfYear, GetLocalTime().tm_yday ); + +/* + * Copyright (c) 2004 by the person(s) listed below. All rights reserved. + * Glenn Maynard + */ diff --git a/stepmania/src/LuaHelpers.h b/stepmania/src/LuaHelpers.h new file mode 100644 index 0000000000..69042f6b04 --- /dev/null +++ b/stepmania/src/LuaHelpers.h @@ -0,0 +1,25 @@ +#ifndef LUA_HELPERS_H +#define LUA_HELPERS_H + +struct lua_State; +namespace Lua +{ + bool RunExpression( const CString &str ); + + void Fail( lua_State *L, const CString &err ); + + /* Add all registered functions into L. */ + void RegisterFunctions( lua_State *L ); + + void PushStack( lua_State *L, bool out ); + void PushStack( lua_State *L, int out ); + void PushStack( lua_State *L, void *out ); + void PushStack( lua_State *L, const CString &out ); + void PopStack( lua_State *L, CString &out ); +}; + +#endif +/* + * Copyright (c) 2004 by the person(s) listed below. All rights reserved. + * Glenn Maynard + */