// Copyright (C) 2012-2014 ChaosForge Ltd // http://chaosforge.org/ // // This file is part of NV Libraries. // For conditions of distribution and use, see copyright notice in nv.hh #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/core/string.hh" using namespace nv; // stack_guard 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_ref 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( 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( 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( 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( 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( LOG_ERROR, "Could not resolve table!" ); // TODO : error handling } } lua::table_guard::~table_guard() { lua_settop( m_state, m_level ); } size_t lua::table_guard::get_size() { return lua_rawlen( m_state, -1 ); } bool lua::table_guard::has_field( string_ref element ) { lua_getfield( m_state, -1, element.data() ); bool result = !( lua_isnil( m_state, -1 ) ); lua_pop( m_state, 1 ); return result; } string lua::table_guard::get_std_string( string_ref element, string_ref defval /*= string_ref() */ ) { 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_ref element, string_ref defval /*= string_ref() */ ) { 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_ref 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_ref 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_ref 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_ref 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_ref element, float defval /*= 0.0 */ ) { lua_getfield( m_state, -1, element.data() ); float result = lua_type( m_state, -1 ) == LUA_TNUMBER ? (float)lua_tonumber( m_state, -1 ) : defval; lua_pop( m_state, 1 ); return result; } bool lua::table_guard::get_boolean( string_ref 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_ref 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_ref 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_ref 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_ref 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( nv::LOG_TRACE, "Lua state created" ); } int lua::state::load_string( string_ref code, string_ref name ) { NV_LOG( nv::LOG_TRACE, "Loading Lua string '" << name << "'"); return luaL_loadbuffer( m_state, code.data(), code.length(), name.data() ); } int lua::state::load_stream( std::istream& stream, string_ref name ) { NV_LOG( nv::LOG_NOTICE, "Loading Lua stream '" << name << "'"); return load_string( std::string( (std::istreambuf_iterator(stream)), std::istreambuf_iterator()), name ); } int lua::state::load_file( string_ref filename ) { NV_LOG( nv::LOG_NOTICE, "Loading Lua file '" << filename << "'"); return luaL_loadfile( m_state, filename.data() ); } bool lua::state::do_string( string_ref code, string_ref name, int rvalues ) { lua::stack_guard( this ); int result = load_string(code,name); if (result) { NV_LOG( 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_stream( std::istream& stream, string_ref name ) { lua::stack_guard( this ); int result = load_stream(stream,name); if (result) { NV_LOG( nv::LOG_WARNING, "Failed to open stream " << name << ": " << lua_tostring(m_state, -1)); return false; } return do_current( name ) == 0; } bool lua::state::do_file( string_ref filename ) { lua::stack_guard( this ); int result = load_file(filename); if (result) { NV_LOG( 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_ref name, int rvalues ) { int result = lua_pcall(m_state, 0, rvalues, 0); if (result) { NV_LOG( 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( LOG_DEBUG, "Stack dump (" << top << ")"); for ( int i = 0; i < top; ++i ) { NV_LOG( 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_ref 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_THROW( runtime_error, lua_name.to_string() + " type not registered!" ); } deep_pointer_copy( -1, o ); return lua::ref( luaL_ref( m_state, LUA_REGISTRYINDEX ) ); } lua::ref lua::state::register_proto( string_ref id, string_ref storage ) { stack_guard guard( this ); lua_getglobal( m_state, storage.data() ); if ( lua_isnil( m_state, -1 ) ) { NV_THROW( runtime_error, storage.to_string() + " storage not registered!" ); } lua_getfield( m_state, -1, id.data() ); if ( lua_isnil( m_state, -1 ) ) { NV_THROW( runtime_error, id.to_string() + " not found in " + storage.to_string() + " storage!" ); } return lua::ref( luaL_ref( m_state, LUA_REGISTRYINDEX ) ); } void lua::state::register_native_object_method( string_ref lua_name, string_ref name, lfunction f ) { stack_guard guard( this ); lua_getglobal( m_state, lua_name.data() ); if ( lua_isnil( m_state, -1 ) ) { NV_THROW( runtime_error, lua_name.to_string() + " 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_pushstring( m_state, "__ptr" ); lua_pushlightuserdata( m_state, obj ); lua_rawset( m_state, -3 ); } } void nv::lua::state::store_metadata( ref object_index, string_ref 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_ref 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_ref 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_ref 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_ref name, void* o ) { if ( o == nullptr ) return; stack_guard guard( this ); lua_getglobal( m_state, name.data() ); if ( lua_isnil( m_state, -1 ) ) { NV_THROW( runtime_error, name.to_string() + " type not registered!" ); } deep_pointer_copy( -1, o ); lua_setglobal( m_state, name.data() ); }