diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cd95072565fe..3d7837df8282 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -954,6 +954,7 @@ set(wesnoth-main_SRC scripting/lua_game_launcher.cpp scripting/lua_kernel_base.cpp scripting/lua_types.cpp + scripting/mapgen_lua_kernel.cpp settings.cpp side_filter.cpp game_initialization/singleplayer.cpp diff --git a/src/SConscript b/src/SConscript index 04d5cff9f34c..ef91013c74af 100644 --- a/src/SConscript +++ b/src/SConscript @@ -527,6 +527,7 @@ wesnoth_sources = Split(""" scripting/lua_game_launcher.cpp scripting/lua_kernel_base.cpp scripting/lua_types.cpp + scripting/mapgen_lua_kernel.cpp settings.cpp side_filter.cpp game_initialization/singleplayer.cpp diff --git a/src/game_errors.hpp b/src/game_errors.hpp index e56b47dd0ad1..35023f3823a7 100644 --- a/src/game_errors.hpp +++ b/src/game_errors.hpp @@ -47,6 +47,14 @@ struct game_error : public error { game_error(const std::string& msg) : error("game_error: " + msg) {} }; +/** + * Error used to report an error in a lua script or in the lua interpreter. + */ +struct lua_error : public error { + lua_error(const std::string& msg) : error("lua_error: " + msg) {} + lua_error(const std::string& msg, const std::string& context) : error(context + ": " + msg) {} +}; + /** * Exception used to signal that the user has decided to abort a game, * and to load another game instead. diff --git a/src/game_events/action_wml.cpp b/src/game_events/action_wml.cpp index a6220d55eb0d..2530e97479fb 100644 --- a/src/game_events/action_wml.cpp +++ b/src/game_events/action_wml.cpp @@ -643,6 +643,8 @@ static void on_replay_error(const std::string& message, bool /*b*/) // This allows to perform scripting in WML that will use the same code path as player actions, for example. WML_HANDLER_FUNCTION(do_command, /*event_info*/, cfg) { + //TODO: don't allow this if we are in a whiteboard applied context. + static const std::set allowed_tags = boost::assign::list_of("attack")("move")("recruit")("recall")("disband")("fire_event")("lua_ai"); const bool is_too_early = resources::gamedata->phase() != game_data::START && resources::gamedata->phase() != game_data::PLAY; diff --git a/src/generators/lua_map_generator.cpp b/src/generators/lua_map_generator.cpp index e925d7936cd1..703f6ab10db0 100644 --- a/src/generators/lua_map_generator.cpp +++ b/src/generators/lua_map_generator.cpp @@ -15,154 +15,20 @@ #include "lua_map_generator.hpp" #include "config.hpp" -#include "log.hpp" -#include "lua/lauxlib.h" -#include "lua/lua.h" -#include "lua/lualib.h" - -#include "mt_rng.hpp" - -#ifdef DEBUG_LUA -#include "scripting/debug_lua.hpp" -#endif - -#include "scripting/lua_api.hpp" +#include "game_errors.hpp" +#include "scripting/mapgen_lua_kernel.hpp" #include #include -static lg::log_domain log_mapgen("mapgen"); -#define ERR_NG LOG_STREAM(err, log_mapgen) -#define LOG_NG LOG_STREAM(info, log_mapgen) -#define DBG_NG LOG_STREAM(debug, log_mapgen) - -// Add compiler directive suppressing unused variable warning -#if defined(__GNUC__) || defined(__clang__) || defined(__MINGW32__) -#define ATTR_UNUSED( x ) __attribute__((unused)) x -#else -#define ATTR_UNUSED( x ) x -#endif - -// Begin lua rng bindings - -using rand_rng::mt_rng; - -static const char * Rng = "Rng"; - -static int impl_rng_create(lua_State* L); -static int impl_rng_destroy(lua_State* L); -static int impl_rng_seed(lua_State* L); -static int impl_rng_draw(lua_State* L); - -static void initialize_lua_state(lua_State * L) -{ - // Open safe libraries. - // Debug and OS are not, but most of their functions will be disabled below. - static const luaL_Reg safe_libs[] = { - { "", luaopen_base }, - { "table", luaopen_table }, - { "string", luaopen_string }, - { "math", luaopen_math }, - { "debug", luaopen_debug }, - { "os", luaopen_os }, - { NULL, NULL } - }; - for (luaL_Reg const *lib = safe_libs; lib->func; ++lib) - { - luaL_requiref(L, lib->name, lib->func, 1); - lua_pop(L, 1); /* remove lib */ - } - - // Disable functions from os which we don't want. - lua_getglobal(L, "os"); - lua_pushnil(L); - while(lua_next(L, -2) != 0) { - lua_pop(L, 1); - char const* function = lua_tostring(L, -1); - if(strcmp(function, "clock") == 0 || strcmp(function, "date") == 0 - || strcmp(function, "time") == 0 || strcmp(function, "difftime") == 0) continue; - lua_pushnil(L); - lua_setfield(L, -3, function); - } - lua_pop(L, 1); - - // Disable functions from debug which we don't want. - lua_getglobal(L, "debug"); - lua_pushnil(L); - while(lua_next(L, -2) != 0) { - lua_pop(L, 1); - char const* function = lua_tostring(L, -1); - if(strcmp(function, "traceback") == 0) continue; - lua_pushnil(L); - lua_setfield(L, -3, function); - } - lua_pop(L, 1); - - lua_settop(L, 0); - - // Add mersenne twister rng wrapper - - luaL_newmetatable(L, Rng); - - static luaL_Reg const callbacks[] = { - { "create", &impl_rng_create}, - { "__gc", &impl_rng_destroy}, - { "seed", &impl_rng_seed}, - { "draw", &impl_rng_draw}, - { NULL, NULL } - }; - luaL_setfuncs(L, callbacks, 0); - - lua_pushvalue(L, -1); //make a copy of this table, set it to be its own __index table - lua_setfield(L, -2, "__index"); - - lua_setglobal(L, Rng); -} - -int impl_rng_create(lua_State* L) -{ - mt_rng * ATTR_UNUSED(rng) = new ( lua_newuserdata(L, sizeof(mt_rng)) ) mt_rng(); - luaL_setmetatable(L, Rng); - - return 1; -} -int impl_rng_destroy(lua_State* L) -{ - mt_rng * d = static_cast< mt_rng *> (luaL_testudata(L, 1, Rng)); - if (d == NULL) { - ERR_NG << "rng_destroy called on data of type: " << lua_typename( L, lua_type( L, 1 ) ) << std::endl; - ERR_NG << "This may indicate a memory leak, please report at bugs.wesnoth.org" << std::endl; - } else { - d->~mt_rng(); - } - return 0; -} -int impl_rng_seed(lua_State* L) -{ - mt_rng * rng = static_cast(luaL_checkudata(L, 1, Rng)); - std::string seed = luaL_checkstring(L, 2); - - rng->seed_random(seed); - return 0; -} -int impl_rng_draw(lua_State* L) -{ - mt_rng * rng = static_cast(luaL_checkudata(L, 1, Rng)); - - lua_pushnumber(L, rng->get_next_random()); - return 1; -} - -// End Lua Rng bindings - lua_map_generator::lua_map_generator(const config & cfg) : id_(cfg["id"]) , config_name_(cfg["config_name"]) , create_map_(cfg["create_map"]) , create_scenario_(cfg["create_scenario"]) - , mState_(luaL_newstate()) + , lk_() , generator_data_(cfg) { const char* required[] = {"id", "config_name", "create_map"}; @@ -174,59 +40,18 @@ lua_map_generator::lua_map_generator(const config & cfg) throw mapgen_exception(msg); } } - - initialize_lua_state(mState_); -} - -lua_map_generator::~lua_map_generator() -{ - lua_close(mState_); } std::string lua_map_generator::create_map() { - { - int errcode = luaL_loadstring(mState_, create_map_.c_str()); - if (errcode != LUA_OK) { - std::string msg = "Error when running lua_map_generator create_map.\n"; - msg += "The generator was: " + config_name_ + "\n"; - msg += "Error when parsing create_map function. "; - if (errcode == LUA_ERRSYNTAX) { - msg += "There was a syntax error:\n"; - } else { - msg += "There was a memory error:\n"; - } - msg += lua_tostring(mState_, -1); - throw mapgen_exception(msg); - } - } - { - luaW_pushconfig(mState_, generator_data_); - int errcode = lua_pcall(mState_, 1, 1, 0); - if (errcode != LUA_OK) { - std::string msg = "Error when running lua_map_generator create_map.\n"; - msg += "The generator was: " + config_name_ + "\n"; - msg += "Error when running create_map function. "; - if (errcode == LUA_ERRRUN) { - msg += "There was a runtime error:\n"; - } else if (errcode == LUA_ERRERR) { - msg += "There was an error with the attached debugger:\n"; - } else { - msg += "There was a memory or garbage collection error:\n"; - } - msg += lua_tostring(mState_, -1); - throw mapgen_exception(msg); - } - } - if (!lua_isstring(mState_,-1)) { + try { + return lk_.create_map(create_map_.c_str(), generator_data_); + } catch (game::lua_error & e) { std::string msg = "Error when running lua_map_generator create_map.\n"; msg += "The generator was: " + config_name_ + "\n"; - msg += "create_map did not return a string, instead it returned '"; - msg += lua_typename(mState_, lua_type(mState_, -1)); - msg += "'"; + msg += e.what(); throw mapgen_exception(msg); } - return lua_tostring(mState_, -1); } config lua_map_generator::create_scenario() @@ -235,38 +60,12 @@ config lua_map_generator::create_scenario() return map_generator::create_scenario(); } - { - int errcode = luaL_loadstring(mState_, create_scenario_.c_str()); - if (errcode != LUA_OK) { - std::string msg = "Error when running lua_map_generator create_scenario.\n"; - msg += "The generator was: " + config_name_ + "\n"; - msg += "Error when parsing create_scenario function. "; - if (errcode == LUA_ERRSYNTAX) { - msg += "There was a syntax error:\n"; - } else { - msg += "There was a memory error:\n"; - } - msg += lua_tostring(mState_, -1); - throw mapgen_exception(msg); - } - } - { - luaW_pushconfig(mState_, generator_data_); - int errcode = lua_pcall(mState_, 1, 1, 0); - if (errcode != LUA_OK) { - std::string msg = "Error when running lua_map_generator create_scenario.\n"; - msg += "The generator was: " + config_name_ + "\n"; - msg += "Error when running create_scenario function. "; - if (errcode == LUA_ERRRUN) { - msg += "There was a runtime error:\n"; - } else if (errcode == LUA_ERRERR) { - msg += "There was an error with the attached debugger:\n"; - } else { - msg += "There was a memory or garbage collection error:\n"; - } - msg += lua_tostring(mState_, -1); - throw mapgen_exception(msg); - } + try { + return lk_.create_scenario(create_scenario_.c_str(), generator_data_); + } catch (game::lua_error & e) { + std::string msg = "Error when running lua_map_generator create_scenario.\n"; + msg += "The generator was: " + config_name_ + "\n"; + msg += e.what(); + throw mapgen_exception(msg); } - return luaW_checkconfig(mState_, -1); } diff --git a/src/generators/lua_map_generator.hpp b/src/generators/lua_map_generator.hpp index 3640f2a8c4c7..215fa49dd825 100644 --- a/src/generators/lua_map_generator.hpp +++ b/src/generators/lua_map_generator.hpp @@ -18,6 +18,8 @@ #include "config.hpp" #include "map_generator.hpp" +#include "scripting/mapgen_lua_kernel.hpp" + #include struct lua_State; @@ -30,8 +32,6 @@ class lua_map_generator : public map_generator { public: lua_map_generator(const config & cfg); - ~lua_map_generator(); - bool allow_user_config() const { return false; } std::string name() const { return "lua"; } @@ -49,7 +49,7 @@ class lua_map_generator : public map_generator { std::string create_map_; std::string create_scenario_; - lua_State * mState_; + mapgen_lua_kernel lk_; config generator_data_; }; diff --git a/src/scripting/application_lua_kernel.cpp b/src/scripting/application_lua_kernel.cpp index e4317e034099..ee31765c41c6 100644 --- a/src/scripting/application_lua_kernel.cpp +++ b/src/scripting/application_lua_kernel.cpp @@ -104,8 +104,7 @@ void application_lua_kernel::call_script(const config & event_cfg) { luaW_pushconfig(L, event_cfg); //push the config as an argument - pcall_fcn_ptr pcall = pcall_fcn(); - if (!pcall(L, 1, 0)) //call the script from protected mode, there is one argument and we expect no return values. + if (!luaW_pcall(L, 1, 0)) //call the script from protected mode, there is one argument and we expect no return values. { WRN_LUA << "Got an error when executing script:\n" << lua_tostring(L,-1) << std::endl; } diff --git a/src/scripting/game_lua_kernel.cpp b/src/scripting/game_lua_kernel.cpp index 8d1337b3d45d..10ffc537541b 100644 --- a/src/scripting/game_lua_kernel.cpp +++ b/src/scripting/game_lua_kernel.cpp @@ -107,7 +107,6 @@ #include "unit_types.hpp" // for unit_type_data, unit_types, etc #include "util.hpp" // for lexical_cast #include "variable.hpp" // for vconfig, etc -#include "version.hpp" // for do_version_check, etc #include // for bind_t, bind #include // for auto_any_base, etc @@ -153,6 +152,13 @@ void extract_preload_scripts(config const &game_config) preload_config = game_config.child("game_config"); } +void LuaKernel::log_error(char const * msg, char const * context) +{ + lua_kernel_base::log_error(msg, context); + chat_message(context, msg); +} + + namespace { /** * Stack storing the queued_event objects needed for calling WML actions. @@ -760,157 +766,6 @@ static int intf_set_variable(lua_State *L) return 0; } -/** - * Checks if a file exists (not necessarily a Lua script). - * - Arg 1: string containing the file name. - * - Ret 1: boolean - */ -static int intf_have_file(lua_State *L) -{ - char const *m = luaL_checkstring(L, 1); - std::string p = filesystem::get_wml_location(m); - if (p.empty()) { lua_pushboolean(L, false); } - else { lua_pushboolean(L, true); } - return 1; -} - -class lua_filestream -{ -public: - lua_filestream(const std::string& fname) - : pistream_(filesystem::istream_file(fname)) - { - - } - - static const char * lua_read_data(lua_State * /*L*/, void *data, size_t *size) - { - lua_filestream* lfs = static_cast(data); - - //int startpos = lfs->pistream_->tellg(); - lfs->pistream_->read(lfs->buff_, LUAL_BUFFERSIZE); - //int newpos = lfs->pistream_->tellg(); - *size = lfs->pistream_->gcount(); -#if 0 - ERR_LUA << "read bytes from " << startpos << " to " << newpos << " in total " *size << " from steam\n"; - ERR_LUA << "streamstate beeing " - << " goodbit:" << lfs->pistream_->good() - << " endoffile:" << lfs->pistream_->eof() - << " badbit:" << lfs->pistream_->bad() - << " failbit:" << lfs->pistream_->fail() << "\n"; -#endif - return lfs->buff_; - } - - static int lua_loadfile(lua_State *L, const std::string& fname) - { - lua_filestream lfs(fname); - //lua uses '@' to know that this is a file (as opposed to a something as opposed to something loaded via loadstring ) - std::string chunkname = '@' + fname; - LOG_LUA << "starting to read from " << fname << "\n"; - return lua_load(L, &lua_filestream::lua_read_data, &lfs, chunkname.c_str(), NULL); - } -private: - char buff_[LUAL_BUFFERSIZE]; - boost::scoped_ptr pistream_; -}; - -/** - * Loads and executes a Lua file. - * - Arg 1: string containing the file name. - * - Ret *: values returned by executing the file body. - */ -static int intf_dofile(lua_State *L) -{ - char const *m = luaL_checkstring(L, 1); - std::string p = filesystem::get_wml_location(m); - if (p.empty()) - return luaL_argerror(L, 1, "file not found"); - - lua_settop(L, 0); - -#if 1 - try - { - if(lua_filestream::lua_loadfile(L, p)) - return lua_error(L); - } - catch(const std::exception & ex) - { - luaL_argerror(L, 1, ex.what()); - } -#else - //oldcode to be deleted if newcode works - if (luaL_loadfile(L, p.c_str())) - return lua_error(L); -#endif - - lua_call(L, 0, LUA_MULTRET); - return lua_gettop(L); -} - -/** - * Loads and executes a Lua file, if there is no corresponding entry in wesnoth.package. - * Stores the result of the script in wesnoth.package and returns it. - * - Arg 1: string containing the file name. - * - Ret 1: value returned by the script. - */ -static int intf_require(lua_State *L) -{ - char const *m = luaL_checkstring(L, 1); - - // Check if there is already an entry. - - luaW_getglobal(L, "wesnoth", NULL); // [1:fn 2:wesnoth] - lua_pushstring(L, "package"); // [1:fn 2:wesnoth 3:"package"] - lua_rawget(L, -2); // [1:fn 2:wesnoth 3:package] - lua_pushvalue(L, 1); // [1:fn 2:wesnoth 3:package 4:fn] - lua_rawget(L, -2); // [1:fn 2:wesnoth 3:package 4:nil/file] - if (!lua_isnil(L, -1) && !game_config::debug_lua) return 1; // Am I wrong, or this return leaves 4 values on the stack? (neph) - lua_pop(L, 1); - - - std::string p = filesystem::get_wml_location(m); - if (p.empty()) - return luaL_argerror(L, 1, "file not found"); - - // Compile the file. - -#if 1 - try - { - if(lua_filestream::lua_loadfile(L, p)) - throw game::error(lua_tostring(L, -1)); - } - catch(const std::exception & ex) - { - chat_message("Lua error", ex.what()); - ERR_LUA << ex.what() << '\n'; - return 0; - } -#else - //oldcode to be deleted if newcode works - - int res = luaL_loadfile(L, p.c_str()); - if (res) - { - char const *m = lua_tostring(L, -1); - chat_message("Lua error", m); - ERR_LUA << m << '\n'; - return 0; - } -#endif - - // Execute it. - if (!luaW_pcall(L, 0, 1)) return 0; - - // Add the return value to the table. - lua_pushvalue(L, 1); - lua_pushvalue(L, -2); - lua_settable(L, -4); - return 1; -} - /** * Highlights the given location on the map. * - Args 1,2: location. @@ -2435,27 +2290,6 @@ static int intf_scroll_to_tile(lua_State *L) return 0; } -/** - * Compares 2 version strings - which is newer. - * - Args 1,3: version strings - * - Arg 2: comparison operator (string) - * - Ret 1: comparison result - */ -static int intf_compare_versions(lua_State* L) -{ - char const *v1 = luaL_checkstring(L, 1); - - const VERSION_COMP_OP vop = parse_version_op(luaL_checkstring(L, 2)); - if(vop == OP_INVALID) return luaL_argerror(L, 2, "unknown version comparison operator - allowed are ==, !=, <, <=, > and >="); - - char const *v2 = luaL_checkstring(L, 3); - - const bool result = do_version_check(version_info(v1), vop, version_info(v2)); - lua_pushboolean(L, result); - - return 1; -} - /** * Selects and highlights the given location on the map. * - Args 1,2: location. @@ -3465,13 +3299,11 @@ LuaKernel::LuaKernel(const config &cfg) { "add_modification", &intf_add_modification }, { "add_tile_overlay", &intf_add_tile_overlay }, { "clear_messages", &intf_clear_messages }, - { "compare_versions", &intf_compare_versions }, { "copy_unit", &intf_copy_unit }, { "create_unit", &intf_create_unit }, { "debug", &intf_debug }, { "debug_ai", &intf_debug_ai }, { "delay", &intf_delay }, - { "dofile", &intf_dofile }, { "eval_conditional", &intf_eval_conditional }, { "extract_unit", &intf_extract_unit }, { "find_cost_map", &intf_find_cost_map }, @@ -3502,7 +3334,6 @@ LuaKernel::LuaKernel(const config &cfg) { "get_variable", &intf_get_variable }, { "get_village_owner", &intf_get_village_owner }, { "get_villages", &intf_get_villages }, - { "have_file", &intf_have_file }, { "highlight_hex", &intf_highlight_hex }, { "is_enemy", &intf_is_enemy }, { "lock_view", &intf_lock_view }, @@ -3515,7 +3346,6 @@ LuaKernel::LuaKernel(const config &cfg) { "put_recall_unit", &intf_put_recall_unit }, { "put_unit", &intf_put_unit }, { "remove_tile_overlay", &intf_remove_tile_overlay }, - { "require", &intf_require }, { "scroll_to_tile", &intf_scroll_to_tile }, { "select_hex", &intf_select_hex }, { "set_dialog_active", &intf_set_dialog_active }, @@ -3530,7 +3360,6 @@ LuaKernel::LuaKernel(const config &cfg) { "show_dialog", &intf_show_dialog }, { "simulate_combat", &intf_simulate_combat }, { "synchronize_choice", &intf_synchronize_choice }, - { "textdomain", &lua_common::intf_textdomain }, { "tovconfig", &intf_tovconfig }, { "transform_unit", &intf_transform_unit }, { "unit_ability", &intf_unit_ability }, @@ -3540,7 +3369,12 @@ LuaKernel::LuaKernel(const config &cfg) { "view_locked", &intf_view_locked }, { NULL, NULL } }; - luaL_register(L, "wesnoth", callbacks); + lua_getglobal(L, "wesnoth"); + if (!lua_istable(L,-1)) { + lua_newtable(L); + } + luaL_setfuncs(L, callbacks, 0); + lua_setglobal(L, "wesnoth"); // Create the getside metatable. lua_pushlightuserdata(L @@ -3554,16 +3388,6 @@ LuaKernel::LuaKernel(const config &cfg) lua_setfield(L, -2, "__metatable"); lua_rawset(L, LUA_REGISTRYINDEX); - // Create the gettext metatable. - lua_pushlightuserdata(L - , gettextKey); - lua_createtable(L, 0, 2); - lua_pushcfunction(L, lua_common::impl_gettext); - lua_setfield(L, -2, "__call"); - lua_pushstring(L, "message domain"); - lua_setfield(L, -2, "__metatable"); - lua_rawset(L, LUA_REGISTRYINDEX); - // Create the gettype metatable. lua_pushlightuserdata(L , gettypeKey); @@ -3600,20 +3424,6 @@ LuaKernel::LuaKernel(const config &cfg) lua_setfield(L, -2, "__metatable"); lua_rawset(L, LUA_REGISTRYINDEX); - // Create the tstring metatable. - lua_pushlightuserdata(L - , tstringKey); - lua_createtable(L, 0, 4); - lua_pushcfunction(L, lua_common::impl_tstring_concat); - lua_setfield(L, -2, "__concat"); - lua_pushcfunction(L, lua_common::impl_tstring_collect); - lua_setfield(L, -2, "__gc"); - lua_pushcfunction(L, lua_common::impl_tstring_tostring); - lua_setfield(L, -2, "__tostring"); - lua_pushstring(L, "translatable string"); - lua_setfield(L, -2, "__metatable"); - lua_rawset(L, LUA_REGISTRYINDEX); - // Create the unit status metatable. lua_pushlightuserdata(L , ustatusKey); @@ -3655,12 +3465,6 @@ LuaKernel::LuaKernel(const config &cfg) // Create the ai elements table. ai::lua_ai_context::init(L); - // Delete dofile and loadfile. - lua_pushnil(L); - lua_setglobal(L, "dofile"); - lua_pushnil(L); - lua_setglobal(L, "loadfile"); - // Create the game_config variable with its metatable. lua_getglobal(L, "wesnoth"); lua_newuserdata(L, 0); @@ -3687,12 +3491,6 @@ LuaKernel::LuaKernel(const config &cfg) lua_setfield(L, -2, "current"); lua_pop(L, 1); - // Create the package table. - lua_getglobal(L, "wesnoth"); - lua_newtable(L); - lua_setfield(L, -2, "package"); - lua_pop(L, 1); - // Create the wml_actions table. lua_getglobal(L, "wesnoth"); lua_newtable(L); @@ -3717,14 +3515,6 @@ LuaKernel::LuaKernel(const config &cfg) lua_setfield(L, -2, "theme_items"); lua_pop(L, 1); - // Store the error handler. - lua_pushlightuserdata(L - , executeKey); - lua_getglobal(L, "debug"); - lua_getfield(L, -1, "traceback"); - lua_remove(L, -2); - lua_rawset(L, LUA_REGISTRYINDEX); - lua_settop(L, 0); } @@ -3795,10 +3585,10 @@ void LuaKernel::initialize() // Execute the preload scripts. game_config::load_config(preload_config); BOOST_FOREACH(const config &cfg, preload_scripts) { - execute(cfg["code"].str().c_str(), 0, 0); + run(cfg["code"].str().c_str()); } BOOST_FOREACH(const config &cfg, level_.child_range("lua")) { - execute(cfg["code"].str().c_str(), 0, 0); + run(cfg["code"].str().c_str()); } load_game(); @@ -3886,8 +3676,7 @@ void LuaKernel::save_game(config &cfg) * and the extra UMC ones. */ const std::string m = "Tag is already used: [" + i->key + "]"; - chat_message("Lua error", m); - ERR_LUA << m << '\n'; + log_error(m.c_str()); v.erase(i); continue; } @@ -3979,8 +3768,7 @@ bool LuaKernel::run_filter(char const *name, unit const &u) if(!luaW_getglobal(L, name, NULL)) { std::string message = std::string() + "function " + name + " not found"; - chat_message("Lua SUF Error", message); - ERR_LUA << "Lua SUF Error: " << message << std::endl; + log_error(message.c_str(), "Lua SUF Error"); //we pushed nothing and can safeley return. return false; } @@ -3998,13 +3786,6 @@ bool LuaKernel::run_filter(char const *name, unit const &u) return b; } -// This is needed because default args don't work through function pointers -static int luaW_pcall_default_args(lua_State * L, int a, int b) { - return luaW_pcall(L,a,b,false); -} - -pcall_fcn_ptr LuaKernel::pcall_fcn() { return &luaW_pcall_default_args; } //this causes the run() function to use luaW_pcall instead of lua_pcall - ai::lua_ai_context* LuaKernel::create_lua_ai_context(char const *code, ai::engine_lua *engine) { return ai::lua_ai_context::create(mState,code,engine); diff --git a/src/scripting/game_lua_kernel.hpp b/src/scripting/game_lua_kernel.hpp index 4eb30cc7395f..ef65df15ab42 100644 --- a/src/scripting/game_lua_kernel.hpp +++ b/src/scripting/game_lua_kernel.hpp @@ -47,7 +47,7 @@ class LuaKernel : public lua_kernel_base game_events::queued_event const &); bool run_filter(char const *name, unit const &u); - virtual pcall_fcn_ptr pcall_fcn(); + virtual void log_error(char const* msg, char const* context = "Lua error"); ai::lua_ai_context* create_lua_ai_context(char const *code, ai::engine_lua *engine); ai::lua_ai_action_handler* create_lua_ai_action_handler(char const *code, ai::lua_ai_context &context); diff --git a/src/scripting/lua_kernel_base.cpp b/src/scripting/lua_kernel_base.cpp index 1fee3d50ec29..e9884f35c7a3 100644 --- a/src/scripting/lua_kernel_base.cpp +++ b/src/scripting/lua_kernel_base.cpp @@ -16,6 +16,9 @@ #include "global.hpp" +#include "filesystem.hpp" +#include "game_errors.hpp" +#include "game_config.hpp" //for game_config::debug_lua #include "log.hpp" #include "lua/lauxlib.h" #include "lua/lua.h" @@ -26,15 +29,198 @@ #endif #include "scripting/lua_api.hpp" +#include "scripting/lua_common.hpp" + +#include "version.hpp" // for do_version_check, etc #include #include +#include +#include static lg::log_domain log_scripting_lua("scripting/lua"); #define LOG_LUA LOG_STREAM(info, log_scripting_lua) #define WRN_LUA LOG_STREAM(warn, log_scripting_lua) #define ERR_LUA LOG_STREAM(err, log_scripting_lua) +// Callback implementations + +/** + * Checks if a file exists (not necessarily a Lua script). + * - Arg 1: string containing the file name. + * - Ret 1: boolean + */ +static int intf_have_file(lua_State *L) +{ + char const *m = luaL_checkstring(L, 1); + std::string p = filesystem::get_wml_location(m); + if (p.empty()) { lua_pushboolean(L, false); } + else { lua_pushboolean(L, true); } + return 1; +} + +class lua_filestream +{ +public: + lua_filestream(const std::string& fname) + : pistream_(filesystem::istream_file(fname)) + { + + } + + static const char * lua_read_data(lua_State * /*L*/, void *data, size_t *size) + { + lua_filestream* lfs = static_cast(data); + + //int startpos = lfs->pistream_->tellg(); + lfs->pistream_->read(lfs->buff_, LUAL_BUFFERSIZE); + //int newpos = lfs->pistream_->tellg(); + *size = lfs->pistream_->gcount(); +#if 0 + ERR_LUA << "read bytes from " << startpos << " to " << newpos << " in total " *size << " from steam\n"; + ERR_LUA << "streamstate beeing " + << " goodbit:" << lfs->pistream_->good() + << " endoffile:" << lfs->pistream_->eof() + << " badbit:" << lfs->pistream_->bad() + << " failbit:" << lfs->pistream_->fail() << "\n"; +#endif + return lfs->buff_; + } + + static int lua_loadfile(lua_State *L, const std::string& fname) + { + lua_filestream lfs(fname); + //lua uses '@' to know that this is a file (as opposed to a something as opposed to something loaded via loadstring ) + std::string chunkname = '@' + fname; + LOG_LUA << "starting to read from " << fname << "\n"; + return lua_load(L, &lua_filestream::lua_read_data, &lfs, chunkname.c_str(), NULL); + } +private: + char buff_[LUAL_BUFFERSIZE]; + boost::scoped_ptr pistream_; +}; + +/** + * Loads and executes a Lua file. + * - Arg 1: string containing the file name. + * - Ret *: values returned by executing the file body. + */ +static int intf_dofile(lua_State *L) +{ + char const *m = luaL_checkstring(L, 1); + std::string p = filesystem::get_wml_location(m); + if (p.empty()) + return luaL_argerror(L, 1, "file not found"); + + lua_settop(L, 0); + +#if 1 + try + { + if(lua_filestream::lua_loadfile(L, p)) + return lua_error(L); + } + catch(const std::exception & ex) + { + luaL_argerror(L, 1, ex.what()); + } +#else + //oldcode to be deleted if newcode works + if (luaL_loadfile(L, p.c_str())) + return lua_error(L); +#endif + + lua_call(L, 0, LUA_MULTRET); + return lua_gettop(L); +} + + +/** + * Loads and executes a Lua file, if there is no corresponding entry in wesnoth.package. + * Stores the result of the script in wesnoth.package and returns it. + * - Arg 1: string containing the file name. + * - Ret 1: value returned by the script. + */ +static int intf_require(lua_State *L) +{ + char const *m = luaL_checkstring(L, 1); + + // Check if there is already an entry. + + luaW_getglobal(L, "wesnoth", NULL); + lua_pushstring(L, "package"); + lua_rawget(L, -2); + lua_pushvalue(L, 1); + lua_rawget(L, -2); + if (!lua_isnil(L, -1) && !game_config::debug_lua) return 1; + lua_pop(L, 1); + + + std::string p = filesystem::get_wml_location(m); + if (p.empty()) + return luaL_argerror(L, 1, "file not found"); + + // Compile the file. + +#if 1 + try + { + if(lua_filestream::lua_loadfile(L, p)) + throw game::error(lua_tostring(L, -1)); + } + catch(const std::exception & ex) + { + chat_message("Lua error", ex.what()); + ERR_LUA << ex.what() << '\n'; + return 0; + } +#else + //oldcode to be deleted if newcode works + + int res = luaL_loadfile(L, p.c_str()); + if (res) + { + char const *m = lua_tostring(L, -1); + chat_message("Lua error", m); + ERR_LUA << m << '\n'; + return 0; + } +#endif + + // Execute it. + if (!luaW_pcall(L, 0, 1)) return 0; + + // Add the return value to the table. + lua_pushvalue(L, 1); + lua_pushvalue(L, -2); + lua_settable(L, -4); + return 1; +} + + +/** + * Compares 2 version strings - which is newer. + * - Args 1,3: version strings + * - Arg 2: comparison operator (string) + * - Ret 1: comparison result + */ +static int intf_compare_versions(lua_State* L) +{ + char const *v1 = luaL_checkstring(L, 1); + + const VERSION_COMP_OP vop = parse_version_op(luaL_checkstring(L, 2)); + if(vop == OP_INVALID) return luaL_argerror(L, 2, "unknown version comparison operator - allowed are ==, !=, <, <=, > and >="); + + char const *v2 = luaL_checkstring(L, 3); + + const bool result = do_version_check(version_info(v1), vop, version_info(v2)); + lua_pushboolean(L, result); + + return 1; +} + +// End Callback implementations + lua_kernel_base::lua_kernel_base() : mState(luaL_newstate()) { @@ -82,7 +268,71 @@ lua_kernel_base::lua_kernel_base() } lua_pop(L, 1); + // Delete dofile and loadfile. + lua_pushnil(L); + lua_setglobal(L, "dofile"); + lua_pushnil(L); + lua_setglobal(L, "loadfile"); + + // Store the error handler. + lua_pushlightuserdata(L + , executeKey); + lua_getglobal(L, "debug"); + lua_getfield(L, -1, "traceback"); + lua_remove(L, -2); + lua_rawset(L, LUA_REGISTRYINDEX); + lua_pop(L, 1); + + // Create the gettext metatable. + lua_pushlightuserdata(L + , gettextKey); + lua_createtable(L, 0, 2); + lua_pushcfunction(L, lua_common::impl_gettext); + lua_setfield(L, -2, "__call"); + lua_pushstring(L, "message domain"); + lua_setfield(L, -2, "__metatable"); + lua_rawset(L, LUA_REGISTRYINDEX); + + // Create the tstring metatable. + lua_pushlightuserdata(L + , tstringKey); + lua_createtable(L, 0, 4); + lua_pushcfunction(L, lua_common::impl_tstring_concat); + lua_setfield(L, -2, "__concat"); + lua_pushcfunction(L, lua_common::impl_tstring_collect); + lua_setfield(L, -2, "__gc"); + lua_pushcfunction(L, lua_common::impl_tstring_tostring); + lua_setfield(L, -2, "__tostring"); + lua_pushstring(L, "translatable string"); + lua_setfield(L, -2, "__metatable"); + lua_rawset(L, LUA_REGISTRYINDEX); + lua_settop(L, 0); + + // Add some callback from the wesnoth lib + + static luaL_Reg const callbacks[] = { + { "compare_versions", &intf_compare_versions }, + { "dofile", &intf_dofile }, + { "have_file", &intf_have_file }, + { "require", &intf_require }, + { "textdomain", &lua_common::intf_textdomain }, + { NULL, NULL } + }; + + lua_getglobal(L, "wesnoth"); + if (!lua_istable(L,-1)) { + lua_newtable(L); + } + luaL_setfuncs(L, callbacks, 0); + lua_setglobal(L, "wesnoth"); + + // Create the package table. + lua_getglobal(L, "wesnoth"); + lua_newtable(L); + lua_setfield(L, -2, "package"); + lua_pop(L, 1); + } lua_kernel_base::~lua_kernel_base() @@ -90,51 +340,109 @@ lua_kernel_base::~lua_kernel_base() lua_close(mState); } -// This is needed because lua_pcall is actually a macro -static int lua_pcall_fcn(lua_State * L, int a, int b) +void lua_kernel_base::log_error(char const * msg, char const * context) { - return lua_pcall(L,a,b,0); + ERR_LUA << context << ": " << msg; } -// In the "base" configuration we just want to call lua_pcall when running scripts. -pcall_fcn_ptr lua_kernel_base::pcall_fcn() { return &lua_pcall_fcn; } +void lua_kernel_base::throw_exception(char const * msg, char const * context) +{ + throw game::lua_error(msg, context); +} -/** - * Runs a script on a stack containing @a nArgs arguments. - * @return true if the script was successful and @a nRets return values are available. - */ -bool lua_kernel_base::execute(char const *prog, int nArgs, int nRets) +bool lua_kernel_base::protected_call(int nArgs, int nRets) +{ + error_handler eh = boost::bind(&lua_kernel_base::log_error, this, _1, _2 ); + return protected_call(nArgs, nRets, eh); +} + +bool lua_kernel_base::load_string(char const * prog) +{ + error_handler eh = boost::bind(&lua_kernel_base::log_error, this, _1, _2 ); + return load_string(prog, eh); +} + +bool lua_kernel_base::protected_call(int nArgs, int nRets, error_handler e_h) { lua_State *L = mState; - // Compile script into a variadic function. - int res = luaL_loadstring(L, prog); - if (res) - { - char const *m = lua_tostring(L, -1); - chat_message("Lua error", m); - ERR_LUA << m << '\n'; - lua_pop(L, 1); - return true; - } + // Load the error handler before the function and its arguments. + lua_pushlightuserdata(L + , executeKey); + + lua_rawget(L, LUA_REGISTRYINDEX); + lua_insert(L, -2 - nArgs); + + int error_handler_index = lua_gettop(L) - nArgs - 1; + + // Call the function. + int errcode = lua_pcall(L, nArgs, nRets, -2 - nArgs); + tlua_jailbreak_exception::rethrow(); + + // Remove the error handler. + lua_remove(L, error_handler_index); - // Place the function before its arguments. - if (nArgs) - lua_insert(L, -1 - nArgs); + if (errcode != LUA_OK) { + std::string message = lua_tostring(L, -1); - pcall_fcn_ptr f = pcall_fcn(); - return f(L, nArgs, nRets); + std::string context = "When executing, "; + if (errcode == LUA_ERRRUN) { + context += "Lua runtime error: "; + } else if (errcode == LUA_ERRERR) { + context += "Lua error in attached debugger: "; + } else if (errcode == LUA_ERRMEM) { + context += "Lua out of memory error: "; + } else if (errcode == LUA_ERRGCMM) { + context += "Lua error in garbage collection metamethod: "; + } else { + context += "unknown lua error: "; + } + + lua_pop(mState, 1); + + e_h(message.c_str(), context.c_str()); + + return false; + } + + return true; } -void lua_kernel_base::run(const char * prog) { - if (execute(prog, 0, 0)) { - lua_State *L = mState; +bool lua_kernel_base::load_string(char const * prog, error_handler e_h) +{ + int errcode = luaL_loadstring(mState, prog); + if (errcode != LUA_OK) { + std::string msg = lua_tostring(mState, -1); - char const *m = lua_tostring(L, -1); - ERR_LUA << "lua_kernel::run(): " << m << '\n'; - lua_pop(L,1); + std::string context = "When parsing a string to lua, "; - //execute("print(debug.traceback())",0,0); + if (errcode == LUA_ERRSYNTAX) { + msg += " a syntax error: "; + } else if(errcode == LUA_ERRMEM){ + msg += " a memory error: "; + } else if(errcode == LUA_ERRGCMM) { + msg += " an error in garbage collection metamethod: "; + } else { + msg += " an unknown error: "; + } + + lua_pop(mState, 1); + + e_h(msg.c_str(), context.c_str()); + + return false; + } + return true; +} + +// Call load_string and protected call. Make them throw exceptions, and if we catch one, reformat it with signature for this function and log it. +void lua_kernel_base::run(const char * prog) { + try { + error_handler eh = boost::bind(&lua_kernel_base::throw_exception, this, _1, _2 ); + load_string(prog, eh); + protected_call(0, 0, eh); + } catch (game::lua_error & e) { + lua_kernel_base::log_error(e.what(), "In function lua_kernel::run()"); } } diff --git a/src/scripting/lua_kernel_base.hpp b/src/scripting/lua_kernel_base.hpp index 2c09d2b6c7d4..8fe53ed8d933 100644 --- a/src/scripting/lua_kernel_base.hpp +++ b/src/scripting/lua_kernel_base.hpp @@ -16,25 +16,35 @@ #define SCRIPTING_LUA_KERNEL_BASE_HPP #include // for string +#include "utils/boost_function_guarded.hpp" struct lua_State; -typedef int (*pcall_fcn_ptr)(lua_State *, int, int); - class lua_kernel_base { -protected: - lua_State *mState; - bool execute(char const *, int, int); public: lua_kernel_base(); virtual ~lua_kernel_base(); - /** Runs a plain script. */ + /** Runs a plain script. Doesn't throw lua_error.*/ void run(char const *prog); void load_package(); - virtual pcall_fcn_ptr pcall_fcn(); //when running scripts, in the "base" kernel type we should just use pcall. But for the in-game kernel, we want to call the luaW_pcall function instead which extends it using things specific to that api, and returns errors on a WML channel + virtual void log_error(char const* msg, char const* context = "Lua error"); + virtual void throw_exception(char const* msg, char const* context = "Lua error"); //throws game::lua_error + + typedef boost::function error_handler; + +protected: + lua_State *mState; + + // Execute a protected call. Error handler is called in case of an error, using syntax for log_error and throw_exception above. Returns true if successful. + bool protected_call(int nArgs, int nRets, error_handler); + // Load a string onto the stack as a function. Returns true if successful, error handler is called if not. + bool load_string(char const * prog, error_handler); + + virtual bool protected_call(int nArgs, int nRets); // select default error handler polymorphically + virtual bool load_string(char const * prog); // select default error handler polymorphically }; #endif diff --git a/src/scripting/mapgen_lua_kernel.cpp b/src/scripting/mapgen_lua_kernel.cpp new file mode 100644 index 000000000000..1cbfa376d95d --- /dev/null +++ b/src/scripting/mapgen_lua_kernel.cpp @@ -0,0 +1,149 @@ +/* + Copyright (C) 2014 by Chris Beck + Part of the Battle for Wesnoth Project http://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ + +#include "scripting/mapgen_lua_kernel.hpp" + +#include "config.hpp" +#include "game_errors.hpp" +#include "log.hpp" +#include "lua/lauxlib.h" +#include "lua/lua.h" +#include "lua/lualib.h" +#include "mt_rng.hpp" +#include "scripting/lua_api.hpp" +#include "scripting/lua_common.hpp" + +#include +#include +#include + +static lg::log_domain log_mapgen("mapgen"); +#define ERR_NG LOG_STREAM(err, log_mapgen) +#define LOG_NG LOG_STREAM(info, log_mapgen) +#define DBG_NG LOG_STREAM(debug, log_mapgen) + +// Add compiler directive suppressing unused variable warning +#if defined(__GNUC__) || defined(__clang__) || defined(__MINGW32__) +#define ATTR_UNUSED( x ) __attribute__((unused)) x +#else +#define ATTR_UNUSED( x ) x +#endif + +// Begin lua rng bindings + +using rand_rng::mt_rng; + +static const char * Rng = "Rng"; + +static int impl_rng_create(lua_State* L) +{ + mt_rng * ATTR_UNUSED(rng) = new ( lua_newuserdata(L, sizeof(mt_rng)) ) mt_rng(); + luaL_setmetatable(L, Rng); + + return 1; +} + +static int impl_rng_destroy(lua_State* L) +{ + mt_rng * d = static_cast< mt_rng *> (luaL_testudata(L, 1, Rng)); + if (d == NULL) { + ERR_NG << "rng_destroy called on data of type: " << lua_typename( L, lua_type( L, 1 ) ) << std::endl; + ERR_NG << "This may indicate a memory leak, please report at bugs.wesnoth.org" << std::endl; + } else { + d->~mt_rng(); + } + return 0; +} + +static int impl_rng_seed(lua_State* L) +{ + mt_rng * rng = static_cast(luaL_checkudata(L, 1, Rng)); + std::string seed = luaL_checkstring(L, 2); + + rng->seed_random(seed); + return 0; +} + +static int impl_rng_draw(lua_State* L) +{ + mt_rng * rng = static_cast(luaL_checkudata(L, 1, Rng)); + + lua_pushnumber(L, rng->get_next_random()); + return 1; +} + +// End Lua Rng bindings + +mapgen_lua_kernel::mapgen_lua_kernel() + : lua_kernel_base() +{ + lua_State *L = mState; + + // Add mersenne twister rng wrapper + luaL_newmetatable(L, Rng); + + static luaL_Reg const callbacks[] = { + { "create", &impl_rng_create}, + { "__gc", &impl_rng_destroy}, + { "seed", &impl_rng_seed}, + { "draw", &impl_rng_draw}, + { NULL, NULL } + }; + luaL_setfuncs(L, callbacks, 0); + + lua_pushvalue(L, -1); //make a copy of this table, set it to be its own __index table + lua_setfield(L, -2, "__index"); + + lua_setglobal(L, Rng); +} + +void mapgen_lua_kernel::run_generator(const char * prog, const config & generator) +{ + load_string(prog, boost::bind(&lua_kernel_base::throw_exception, this, _1, _2)); + luaW_pushconfig(mState, generator); + protected_call(1, 1, boost::bind(&lua_kernel_base::throw_exception, this, _1, _2)); +} + +std::string mapgen_lua_kernel::create_map(const char * prog, const config & generator) // throws game::lua_error +{ + run_generator(prog, generator); + + if (!lua_isstring(mState,-1)) { + std::string msg = "expected a string, found a "; + msg += lua_typename(mState, lua_type(mState, -1)); + lua_pop(mState, 1); + throw game::lua_error(msg.c_str(),"bad return value"); + } + + return lua_tostring(mState, -1); +} + +config mapgen_lua_kernel::create_scenario(const char * prog, const config & generator) // throws game::lua_error +{ + run_generator(prog, generator); + + if (!lua_istable(mState, -1)) { + std::string msg = "expected a config (table), found a "; + msg += lua_typename(mState, lua_type(mState, -1)); + lua_pop(mState, 1); + throw game::lua_error(msg.c_str(),"bad return value"); + } + config result; + if (!luaW_toconfig(mState, -1, result)) { + std::string msg = "expected a config, but it is malformed "; + lua_pop(mState, 1); + throw game::lua_error(msg.c_str(),"bad return value"); + } + return result; +} diff --git a/src/scripting/mapgen_lua_kernel.hpp b/src/scripting/mapgen_lua_kernel.hpp new file mode 100644 index 000000000000..0a5c44c2d241 --- /dev/null +++ b/src/scripting/mapgen_lua_kernel.hpp @@ -0,0 +1,36 @@ +/* + Copyright (C) 2014 by Chris Beck + Part of the Battle for Wesnoth Project http://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ + +#ifndef INCLUDED_MAPGEN_LUA_KERNEL +#define INCLUDED_MAPGEN_LUA_KERNEL + +#include "scripting/lua_kernel_base.hpp" + +class config; + +#include +#include + +class mapgen_lua_kernel : public lua_kernel_base { +public: + mapgen_lua_kernel(); + + std::string create_map(const char * prog, const config & generator); // throws game::lua_error + config create_scenario(const char * prog, const config & generator); // throws game::lua_error + +private: + void run_generator(const char * prog, const config & generator); +}; + +#endif