// Copyright (C) 2012-2015 ChaosForge Ltd // http://chaosforge.org/ // // This file is part of Nova libraries. // For conditions of distribution and use, see copying.txt file in root folder. #include "nv/lua/lua_state.hh" #include "nv/lua/lua_raw.hh" #include "nv/lua/lua_nova.hh" #include "nv/core/logging.hh" #include "nv/stl/string.hh" using namespace nv; // stack_guard #define NV_LUA_ABORT( func, ... ) \ NV_LOG_CRITICAL( "lua::" func " : ", __VA_ARGS__ ) \ NV_ABORT( "lua::" func " : critical error!" ) lua::stack_guard::stack_guard( lua::state* aL ) : L(aL), m_level( lua_gettop(aL->m_state) ) { } lua::stack_guard::stack_guard( lua::state& aL ) : L(&aL), m_level( lua_gettop(aL.m_state) ) { } lua::stack_guard::~stack_guard() { lua_settop( L->m_state, m_level ); } // stack_assert nv::lua::stack_assert::stack_assert( lua::state* aL, int expected ) : L(aL->get_raw()), m_expected( lua_gettop(aL->get_raw() ) + expected ) { } nv::lua::stack_assert::stack_assert( lua::state& aL, int expected ) : L(aL.get_raw()), m_expected( lua_gettop(aL.get_raw() ) + expected ) { } nv::lua::stack_assert::stack_assert( lua_State* aL, int expected ) : L(aL), m_expected( lua_gettop(aL) + expected ) { } lua::stack_assert::~stack_assert() { NV_ASSERT( lua_gettop(L) == m_expected, "Lua stack corruption detected!" ); } // state_wrapper void nv::lua::state_wrapper::push_global_table() { lua_pushglobaltable( m_state ); } void nv::lua::state_wrapper::pop_global_table() { lua_replace( m_state, -2 ); } void lua::state_wrapper::call_get() { lua_gettable( m_state, -2 ); } void lua::state_wrapper::call_get_raw() { lua_rawget( m_state, -2 ); } void lua::state_wrapper::register_native_function( lfunction f, string_view name ) { if ( m_global ) push_global_table(); lua_pushcfunction( m_state, f ); lua_setfield( m_state, -2, name.data() ); if ( m_global ) pop_global_table(); } lua::state_wrapper::~state_wrapper() { if (m_owner) { lua_close( m_state ); } } bool nv::lua::state_wrapper::is_defined( const path& p, bool global ) { if ( !p.resolve( m_state, global ) ) { return false; } bool result = !lua_isnil( m_state, -1 ); lua_pop( m_state, 1 ); return result; } bool nv::lua::state_wrapper::push_function( const path& p, bool global ) { if ( !p.resolve( m_state, global ) ) { NV_LOG_ERROR( "Lua error : not a valid path - ", p.to_string() ); return false; } if ( !lua_isfunction( m_state, -1 ) ) { lua_pop( m_state, 1 ); NV_LOG_ERROR( "Lua error : not a valid function - ", p.to_string() ); return false; } return true; } int nv::lua::state_wrapper::call_function( int nargs, int nresults ) { int status = lua_pcall( m_state, nargs, nresults, 0 ); if ( status != 0 ) { NV_LOG_ERROR( "Lua error : ", lua_tostring( m_state, -1 ) ); lua_pop( m_state, 1 ); } return status; } // table_guard lua::table_guard::table_guard( lua::state* lstate, const path& p, bool global ) : state_wrapper( lstate->get_raw(), false ), m_level(0) { m_global = false; m_level = lua_gettop( m_state ); if ( !p.resolve( m_state, global ) || lua_type(m_state, -1) != LUA_TTABLE ) { NV_LOG_ERROR( "Could not resolve table!" ); // TODO : error handling } } lua::table_guard::table_guard( const table_guard& parent, const path& p ) : state_wrapper( parent.m_state, false ), m_level(0) { m_global = false; m_level = lua_gettop( m_state ); if ( !p.resolve( m_state, false ) || lua_type(m_state, -1) != LUA_TTABLE ) { NV_LOG_ERROR( "Could not resolve table!" ); // TODO : error handling } } lua::table_guard::~table_guard() { lua_settop( m_state, m_level ); } nv::size_t lua::table_guard::get_size() { return lua_rawlen( m_state, -1 ); } bool lua::table_guard::has_field( string_view element ) { lua_getfield( m_state, -1, element.data() ); bool result = !( lua_isnil( m_state, -1 ) ); lua_pop( m_state, 1 ); return result; } std::string lua::table_guard::get_std_string( string_view element, string_view defval /*= string_view() */ ) { lua_getfield( m_state, -1, element.data() ); size_t l = 0; const char* str = nullptr; if ( lua_type( m_state, -1 ) == LUA_TSTRING ) { str = lua_tolstring( m_state, -1, &l ); } else { l = defval.size(); str = defval.data(); } std::string result( str, l ); lua_pop( m_state, 1 ); return result; } const_string lua::table_guard::get_string( string_view element, string_view defval /*= string_view() */ ) { lua_getfield( m_state, -1, element.data() ); size_t l = 0; const char* str = nullptr; if ( lua_type( m_state, -1 ) == LUA_TSTRING ) { str = lua_tolstring( m_state, -1, &l ); } else { l = defval.size(); str = defval.data(); } const_string result( str, l ); lua_pop( m_state, 1 ); return result; } char lua::table_guard::get_char( string_view element, char defval /*= "" */ ) { lua_getfield( m_state, -1, element.data() ); char result = ( lua_type( m_state, -1 ) == LUA_TSTRING && lua_rawlen( m_state, -1 ) > 0 ) ? lua_tostring( m_state, -1 )[0] : defval; lua_pop( m_state, 1 ); return result; } int lua::table_guard::get_integer( string_view element, int defval /*= "" */ ) { lua_getfield( m_state, -1, element.data() ); lua_Integer result = lua_type( m_state, -1 ) == LUA_TNUMBER ? lua_tointeger( m_state, -1 ) : defval; lua_pop( m_state, 1 ); return static_cast< int >( result ); } unsigned lua::table_guard::get_unsigned( string_view element, unsigned defval /*= "" */ ) { lua_getfield( m_state, -1, element.data() ); unsigned result = lua_type( m_state, -1 ) == LUA_TNUMBER ? lua_tounsigned( m_state, -1 ) : defval; lua_pop( m_state, 1 ); return result; } double lua::table_guard::get_double( string_view element, double defval /*= "" */ ) { lua_getfield( m_state, -1, element.data() ); double result = lua_type( m_state, -1 ) == LUA_TNUMBER ? lua_tonumber( m_state, -1 ) : defval; lua_pop( m_state, 1 ); return result; } float nv::lua::table_guard::get_float( string_view element, float defval /*= 0.0 */ ) { lua_getfield( m_state, -1, element.data() ); float result = lua_type( m_state, -1 ) == LUA_TNUMBER ? static_cast( lua_tonumber( m_state, -1 ) ) : defval; lua_pop( m_state, 1 ); return result; } bool lua::table_guard::get_boolean( string_view element, bool defval /*= "" */ ) { lua_getfield( m_state, -1, element.data() ); bool result = lua_type( m_state, -1 ) == LUA_TBOOLEAN ? lua_toboolean( m_state, -1 ) != 0 : defval; lua_pop( m_state, 1 ); return result; } bool nv::lua::table_guard::is_table( string_view element ) { lua_getfield( m_state, -1, element.data() ); bool result = lua_type( m_state, -1 ) == LUA_TTABLE; lua_pop( m_state, 1 ); return result; } bool nv::lua::table_guard::is_number( string_view element ) { lua_getfield( m_state, -1, element.data() ); bool result = lua_type( m_state, -1 ) == LUA_TNUMBER; lua_pop( m_state, 1 ); return result; } bool nv::lua::table_guard::is_boolean( string_view element ) { lua_getfield( m_state, -1, element.data() ); bool result = lua_type( m_state, -1 ) == LUA_TBOOLEAN; lua_pop( m_state, 1 ); return result; } bool nv::lua::table_guard::is_string( string_view element ) { lua_getfield( m_state, -1, element.data() ); bool result = lua_type( m_state, -1 ) == LUA_TSTRING; lua_pop( m_state, 1 ); return result; } // state lua::state::state( lua_State* state ) : state_wrapper( state, false ) { } lua::state::state( bool load_libs /*= false*/ ) : state_wrapper( nullptr, true ) { load_lua_library(); m_owner = true; m_state = luaL_newstate( ); lua_pushcfunction(m_state, luaopen_base); lua_pushliteral(m_state, LUA_TABLIBNAME); lua_call(m_state, 1, 0); { // Preregister 8 references for Handle registry lua_newtable(m_state); NV_ASSERT( luaL_ref( m_state, LUA_REGISTRYINDEX ) == 1, "First reference not 1!" ); for ( uint32 i = 1; i < 8; ++i ) { lua_newtable(m_state); luaL_ref( m_state, LUA_REGISTRYINDEX ); } } if ( load_libs ) { stack_guard guard( this ); static const luaL_Reg lualibs[] = { { "string", luaopen_string }, { "table", luaopen_table }, { "math", luaopen_math }, { NULL, NULL} }; const luaL_Reg *lib = lualibs; for(; lib->func != NULL; lib++) { lua_pushcfunction( m_state, lib->func ); lua_call(m_state, 0, 1); lua_setglobal( m_state, lib->name ); } register_nova( this ); } NV_LOG_TRACE( "Lua state created" ); } int lua::state::load_string( string_view code, string_view name ) { NV_LOG_TRACE( "Loading Lua string '", name, "'"); return luaL_loadbuffer( m_state, code.data(), code.length(), name.data() ); } int lua::state::load_file( string_view filename ) { NV_LOG_NOTICE( "Loading Lua file '", filename, "'"); return luaL_loadfile( m_state, filename.data() ); } bool lua::state::do_string( string_view code, string_view name, int rvalues ) { lua::stack_guard( this ); int result = load_string(code,name); if (result) { NV_LOG_WARNING( "Failed to load string ", name, ": ", lua_tostring(m_state, -1)); return false; } return do_current( name, rvalues ) == 0; } bool lua::state::do_file( string_view filename ) { lua::stack_guard( this ); int result = load_file(filename); if (result) { NV_LOG_WARNING( "Failed to open file ", filename, ": ", lua_tostring( m_state, -1 ) ); return false; } return do_current( filename ) == 0; } int lua::state::do_current( string_view name, int rvalues ) { int result = lua_pcall(m_state, 0, rvalues, 0); if (result) { NV_LOG_WARNING( "Failed to run script ", name, ": ", lua_tostring( m_state, -1 ) ); lua_pop( m_state, 1 ); } return result; } int lua::state::get_stack_size() { return lua_gettop( m_state ); } void lua::state::log_stack() { int top = lua_gettop(m_state); NV_LOG_DEBUG( "Stack dump (", top, ")"); for ( int i = 0; i < top; ++i ) { NV_LOG_DEBUG( "#", i+1, " - ", lua_typename(m_state, lua_type(m_state, i+1) ), " = ", nlua_typecontent(m_state, i+1) ); } } lua_State* lua::state::get_raw() { return m_state; } lua::ref lua::state::register_object( void* o, string_view lua_name ) { if ( o == nullptr ) return lua::ref( lua::ref::none ); stack_guard guard( this ); lua_getglobal( m_state, lua_name.data() ); if ( lua_isnil( m_state, -1 ) ) { NV_LUA_ABORT( "state::register_object", lua_name, " type not registered!" ); } deep_pointer_copy( -1, o ); return lua::ref( luaL_ref( m_state, LUA_REGISTRYINDEX ) ); } lua::ref lua::state::register_proto( string_view id, string_view storage ) { stack_guard guard( this ); lua_getglobal( m_state, storage.data() ); if ( lua_isnil( m_state, -1 ) ) { NV_LUA_ABORT( "state::register_proto", "\"", storage, "\" storage not registered!" ); } lua_getfield( m_state, -1, id.data() ); if ( lua_isnil( m_state, -1 ) ) { NV_LUA_ABORT( "state::register_proto", "\"", id, "\" not found in \"", storage, "\"!" ); } return lua::ref( luaL_ref( m_state, LUA_REGISTRYINDEX ) ); } void lua::state::register_native_object_method( string_view lua_name, string_view name, lfunction f ) { stack_guard guard( this ); lua_getglobal( m_state, lua_name.data() ); if ( lua_isnil( m_state, -1 ) ) { NV_LUA_ABORT( "state::register_native_object_method", "\"", lua_name, "\" type not registered!" ); } lua_pushcfunction( m_state, f ); lua_setfield( m_state, -2, name.data() ); } void lua::state::unregister_object( ref object_index ) { if ( !object_index.is_valid() ) return; stack_guard guard( this ); lua_rawgeti( m_state, LUA_REGISTRYINDEX, object_index.get() ); lua_pushliteral( m_state, "__ptr" ); lua_pushboolean( m_state, false ); lua_rawset( m_state, -3 ); lua_pop( m_state, 1 ); luaL_unref( m_state, LUA_REGISTRYINDEX, object_index.get() ); } void lua::state::deep_pointer_copy( int index, void* obj ) { index = lua_absindex( m_state, index ); lua_newtable( m_state ); lua_pushnil( m_state ); bool has_functions = false; bool has_metatable = false; while ( lua_next( m_state, index ) != 0 ) { if ( lua_isfunction( m_state, -1 ) ) has_functions = true; else if ( lua_istable( m_state, -1 ) ) { deep_pointer_copy( -1, obj ); lua_insert( m_state, -2 ); lua_pop( m_state, 1 ); } lua_pushvalue( m_state, -2 ); lua_insert( m_state, -2 ); lua_settable( m_state, -4 ); } if ( lua_getmetatable( m_state, -2 ) ) { lua_setmetatable( m_state, -2 ); has_metatable = true; } if ( has_functions || has_metatable ) { lua_pushliteral( m_state, "__ptr" ); lua_pushlightuserdata( m_state, obj ); lua_rawset( m_state, -3 ); } } void nv::lua::state::store_metadata( ref object_index, string_view metaname, void* pointer ) { if ( !object_index.is_valid() ) return; lua_rawgeti( m_state, LUA_REGISTRYINDEX, object_index.get() ); lua_pushlstring( m_state, metaname.data(), metaname.size() ); lua_pushlightuserdata( m_state, pointer ); lua_rawset( m_state, -3 ); lua_pop( m_state, 1 ); } void nv::lua::state::register_enum( string_view name, int value ) { lua_pushinteger( m_state, value ); lua_setglobal( m_state, name.data() ); } nv::lua::ref nv::lua::state::register_handle_component_impl( string_view id, bool empty ) { int args = empty ? 1 : 2; NV_LUA_STACK_ASSERT( m_state, -args ); if ( lua_isnil( m_state, -1 ) || (!empty && lua_isnil( m_state, -2 )) ) { lua_pop( m_state, args ); return ref(); } if (empty) lua_createtable( m_state, 0, 0 ); else nlua_deepcopy( m_state, -2 ); lua_pushlstring( m_state, id.data(), id.size() ); lua_pushvalue( m_state, -2 ); lua_rawset( m_state, -4 ); lua_replace( m_state, -2 ); ref result = lua::ref( luaL_ref( m_state, LUA_REGISTRYINDEX ) ); if (!empty) lua_pop( m_state, 1 ); return result; } void nv::lua::state::unregister_handle_component_impl( string_view id ) { NV_LUA_STACK_ASSERT( m_state, -1 ); if ( lua_isnil( m_state, -1 ) ) { lua_pop( m_state, 1 ); return; } lua_pushlstring( m_state, id.data(), id.size() ); lua_pushnil( m_state ); lua_rawset( m_state, -3 ); lua_pop( m_state, 1 ); } void nv::lua::state::register_singleton( string_view name, void* o ) { if ( o == nullptr ) return; stack_guard guard( this ); lua_getglobal( m_state, name.data() ); if ( lua_isnil( m_state, -1 ) ) { NV_LUA_ABORT( "state::register_singleton", "\"", name, "\" type not registered!" ); } deep_pointer_copy( -1, o ); lua_setglobal( m_state, name.data() ); }