// Copyright (C) 2012-2013 ChaosForge / Kornel Kisielewicz // 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/logging.hh" #include "nv/string.hh" #include "nv/root.hh" #include "nv/types.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 ); } // 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, const char* name ) { if ( m_global ) push_global_table(); lua_pushcfunction( m_state, f ); lua_setfield( m_state, -2, name ); 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 ) { std::string error = lua_tostring( m_state, -1 ); lua_pop( m_state, 1 ); NV_LOG( LOG_ERROR, "Lua error : " << error ) } 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 ) ) { // 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 ) ) { // 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( const string& element ) { lua_getfield( m_state, -1, element.c_str() ); bool result = lua_isnil( m_state, -1 ); lua_pop( m_state, 1 ); return result; } string lua::table_guard::get_string( const string& element, const string& defval /*= "" */ ) { lua_getfield( m_state, -1, element.c_str() ); string result( ( lua_type( m_state, -1 ) == LUA_TSTRING ) ? lua_tostring( m_state, -1 ) : defval ); lua_pop( m_state, 1 ); return result; } char lua::table_guard::get_char( const string& element, char defval /*= "" */ ) { lua_getfield( m_state, -1, element.c_str() ); 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( const string& element, int defval /*= "" */ ) { lua_getfield( m_state, -1, element.c_str() ); 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( const string& element, unsigned defval /*= "" */ ) { lua_getfield( m_state, -1, element.c_str() ); 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( const string& element, double defval /*= "" */ ) { lua_getfield( m_state, -1, element.c_str() ); double result = lua_type( m_state, -1 ) == LUA_TNUMBER ? lua_tonumber( m_state, -1 ) : defval; lua_pop( m_state, 1 ); return result; } bool lua::table_guard::get_boolean( const string& element, bool defval /*= "" */ ) { lua_getfield( m_state, -1, element.c_str() ); bool result = lua_type( m_state, -1 ) == LUA_TBOOLEAN ? lua_toboolean( m_state, -1 ) != 0 : defval; lua_pop( m_state, 1 ); return result; } // state lua::state::state( lua_State* state ) : state_wrapper( state, false ), m_type_database( nullptr ) { } lua::state::state( bool load_libs /*= false*/ ) : state_wrapper( nullptr, true ), m_type_database( nullptr ) { 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); if ( load_libs ) { m_type_database = new type_database(); 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++) { lib->func( m_state ); } register_nova( this ); } NV_LOG( nv::LOG_TRACE, "Lua state created" ); } int lua::state::load_string( const std::string& code, const std::string& name ) { NV_LOG( nv::LOG_TRACE, "Loading Lua string '" << name << "'"); return luaL_loadbuffer( m_state, code.c_str(), code.length(), name.c_str() ); } int lua::state::load_stream( std::istream& stream, const std::string& 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( const std::string& filename ) { NV_LOG( nv::LOG_NOTICE, "Loading Lua file '" << filename << "'"); return luaL_loadfile( m_state, filename.c_str() ); } bool lua::state::do_string( const std::string& code, const std::string& 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, const std::string& 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( const std::string& 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( const std::string& 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::reference lua::state::register_object( object * o, const char* lua_name ) { if ( o == nullptr ) return ref_none; stack_guard guard( this ); lua_getglobal( m_state, lua_name ); if ( lua_isnil( m_state, -1 ) ) { NV_THROW( runtime_error, std::string( lua_name ) + " type not registered!" ); } deep_pointer_copy( -1, o ); return luaL_ref( m_state, LUA_REGISTRYINDEX ); } lua::reference lua::state::register_proto( object * o, const char* storage ) { stack_guard guard( this ); lua_getglobal( m_state, storage ); if ( lua_isnil( m_state, -1 ) ) { NV_THROW( runtime_error, std::string( storage ) + " storage not registered!" ); } lua_getfield( m_state, -1, o->get_id().c_str() ); if ( lua_isnil( m_state, -1 ) ) { NV_THROW( runtime_error, std::string( o->get_id() ) + " not found in " + std::string( storage ) + " storage!" ); } return luaL_ref( m_state, LUA_REGISTRYINDEX ); } lua::reference lua::state::register_object( object * o ) { if ( o == nullptr || m_type_database == nullptr ) return ref_none; type_entry* t = m_type_database->get_type(typeid(*o)); if ( t == nullptr ) return ref_none; return register_object( o, t->name.c_str() ); } void lua::state::register_native_object_method( const char* lua_name, const char* name, lfunction f ) { stack_guard guard( this ); lua_getglobal( m_state, lua_name ); if ( lua_isnil( m_state, -1 ) ) { NV_THROW( runtime_error, std::string( lua_name ) + " type not registered!" ); } lua_pushcfunction( m_state, f ); lua_setfield( m_state, -2, name ); } void lua::state::unregister_object( object * o ) { if (!o) return; stack_guard guard( this ); lua_rawgeti( m_state, LUA_REGISTRYINDEX, o->get_lua_index() ); lua_pushstring( m_state, "__ptr" ); lua_pushboolean( m_state, false ); lua_rawset( m_state, -3 ); lua_pop( m_state, 1 ); luaL_unref( m_state, LUA_REGISTRYINDEX, o->get_lua_index() ); } 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 lua::state::register_enum_values( const std::string& name, const std::string& prefix /*= std::string() */ ) { type_entry* et = m_type_database->get_type( name ); for ( const auto& entry : et->enum_list ) { lua_pushinteger( m_state, entry.value ); if ( prefix.empty() ) { lua_setglobal( m_state, entry.name.c_str() ); } else { lua_setglobal( m_state, ( prefix + entry.name ).c_str() ); } } } void nv::lua::state::store_metadata( object* o, const std::string& metaname, void* pointer ) { if (!o) return; stack_guard guard( this ); lua_rawgeti( m_state, LUA_REGISTRYINDEX, o->get_lua_index() ); lua_pushstring( m_state, metaname.c_str() ); lua_pushlightuserdata( m_state, pointer ); lua_rawset( m_state, -3 ); lua_pop( m_state, 1 ); } nv::lua::state::~state() { if (m_type_database != nullptr ) { delete m_type_database; } }