
- Now uses the internal (old) require function to load package.lua, and the external (new) require function to load other packages.
806 lines
25 KiB
C++
806 lines
25 KiB
C++
/*
|
|
Copyright (C) 2014 - 2017 by Chris Beck <render787@gmail.com>
|
|
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/lua_kernel_base.hpp"
|
|
|
|
#include "exceptions.hpp"
|
|
#include "game_config.hpp"
|
|
#include "game_errors.hpp"
|
|
#include "gui/widgets/settings.hpp"
|
|
#include "log.hpp"
|
|
#include "lua_jailbreak_exception.hpp" // for lua_jailbreak_exception
|
|
#include "random.hpp"
|
|
#include "seed_rng.hpp"
|
|
|
|
#ifdef DEBUG_LUA
|
|
#include "scripting/debug_lua.hpp"
|
|
#endif
|
|
|
|
#include "scripting/lua_common.hpp"
|
|
#include "scripting/lua_cpp_function.hpp"
|
|
#include "scripting/lua_fileops.hpp"
|
|
#include "scripting/lua_formula_bridge.hpp"
|
|
#include "scripting/lua_gui2.hpp"
|
|
#include "scripting/lua_map_location_ops.hpp"
|
|
#include "scripting/lua_rng.hpp"
|
|
#include "scripting/push_check.hpp"
|
|
|
|
#include "version.hpp" // for do_version_check, etc
|
|
#include "video.hpp"
|
|
|
|
#include "serialization/string_utils.hpp"
|
|
#include "utils/functional.hpp"
|
|
#include "utils/name_generator.hpp"
|
|
#include "utils/markov_generator.hpp"
|
|
#include "utils/context_free_grammar_generator.hpp"
|
|
|
|
#include <cstring>
|
|
#include <exception>
|
|
#include <new>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#include "lua/lauxlib.h"
|
|
#include "lua/lua.h"
|
|
#include "lua/lualib.h"
|
|
|
|
static lg::log_domain log_scripting_lua("scripting/lua");
|
|
#define DBG_LUA LOG_STREAM(debug, log_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)
|
|
|
|
// Registry key for metatable
|
|
static const char * Gen = "name generator";
|
|
|
|
// Callback implementations
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Replacement print function -- instead of printing to std::cout, print to the command log.
|
|
* Intended to be bound to this' command_log at registration time.
|
|
*/
|
|
int lua_kernel_base::intf_print(lua_State* L)
|
|
{
|
|
DBG_LUA << "intf_print called:\n";
|
|
size_t nargs = lua_gettop(L);
|
|
|
|
for (size_t i = 1; i <= nargs; ++i) {
|
|
const char * str = lua_tostring(L,i);
|
|
if (!str) {
|
|
str = "";
|
|
}
|
|
if (i > 1) {
|
|
cmd_log_ << "\t"; //separate multiple args with tab character
|
|
}
|
|
cmd_log_ << str;
|
|
DBG_LUA << "'" << str << "'\n";
|
|
}
|
|
|
|
cmd_log_ << "\n";
|
|
DBG_LUA << "\n";
|
|
|
|
return 0;
|
|
}
|
|
|
|
template<lua_kernel_base::video_function callback>
|
|
int video_dispatch(lua_State *L) {
|
|
return lua_kernel_base::get_lua_kernel<lua_kernel_base>(L).video_dispatch_impl(L, callback);
|
|
}
|
|
|
|
// The show-dialog call back is here implemented as a method of lua kernel, since it needs a pointer to external object CVideo
|
|
int lua_kernel_base::video_dispatch_impl(lua_State* L, lua_kernel_base::video_function callback)
|
|
{
|
|
return callback(L, CVideo::get_singleton());
|
|
}
|
|
|
|
// The show lua console callback is similarly a method of lua kernel
|
|
int lua_kernel_base::intf_show_lua_console(lua_State *L)
|
|
{
|
|
if (cmd_log_.external_log_) {
|
|
std::string message = "There is already an external logger attached to this lua kernel, you cannot open the lua console right now.";
|
|
log_error(message.c_str());
|
|
cmd_log_ << message << "\n";
|
|
return 0;
|
|
}
|
|
|
|
return lua_gui2::show_lua_console(L, CVideo::get_singleton(), this);
|
|
}
|
|
|
|
static int impl_name_generator_call(lua_State *L)
|
|
{
|
|
name_generator* gen = static_cast<name_generator*>(lua_touserdata(L, 1));
|
|
lua_pushstring(L, gen->generate().c_str());
|
|
return 1;
|
|
}
|
|
|
|
static int impl_name_generator_collect(lua_State *L)
|
|
{
|
|
name_generator* gen = static_cast<name_generator*>(lua_touserdata(L, 1));
|
|
gen->~name_generator();
|
|
return 0;
|
|
}
|
|
|
|
static int intf_name_generator(lua_State *L)
|
|
{
|
|
std::string type = luaL_checkstring(L, 1);
|
|
name_generator* gen = nullptr;
|
|
try {
|
|
if(type == "markov" || type == "markov_chain") {
|
|
std::vector<std::string> input;
|
|
if(lua_istable(L, 2)) {
|
|
input = lua_check<std::vector<std::string>>(L, 2);
|
|
} else {
|
|
input = utils::parenthetical_split(luaL_checkstring(L, 2), ',');
|
|
}
|
|
int chain_sz = luaL_optinteger(L, 3, 2);
|
|
int max_len = luaL_optinteger(L, 4, 12);
|
|
gen = new(L) markov_generator(input, chain_sz, max_len);
|
|
// Ensure the pointer didn't change when cast
|
|
assert(static_cast<void*>(gen) == dynamic_cast<markov_generator*>(gen));
|
|
} else if(type == "context_free" || type == "cfg" || type == "CFG") {
|
|
if(lua_istable(L, 2)) {
|
|
std::map<std::string, std::vector<std::string>> data;
|
|
for(lua_pushnil(L); lua_next(L, 2); lua_pop(L, 1)) {
|
|
if(!lua_isstring(L, -2)) {
|
|
lua_pushstring(L, "CFG generator: invalid nonterminal name (must be a string)");
|
|
return lua_error(L);
|
|
}
|
|
if(lua_isstring(L, -1)) {
|
|
data[lua_tostring(L,-2)] = utils::split(lua_tostring(L,-1),'|');
|
|
} else if(lua_istable(L, -1)) {
|
|
data[lua_tostring(L,-2)] = lua_check<std::vector<std::string>>(L, -1);
|
|
} else {
|
|
lua_pushstring(L, "CFG generator: invalid noterminal value (must be a string or list of strings)");
|
|
return lua_error(L);
|
|
}
|
|
}
|
|
if(!data.empty()) {
|
|
gen = new(L) context_free_grammar_generator(data);
|
|
}
|
|
} else {
|
|
gen = new(L) context_free_grammar_generator(luaL_checkstring(L, 2));
|
|
}
|
|
if(gen) {
|
|
assert(static_cast<void*>(gen) == dynamic_cast<context_free_grammar_generator*>(gen));
|
|
}
|
|
} else {
|
|
return luaL_argerror(L, 1, "should be either 'markov_chain' or 'context_free'");
|
|
}
|
|
}
|
|
catch (const name_generator_invalid_exception& ex) {
|
|
lua_pushstring(L, ex.what());
|
|
return lua_error(L);
|
|
}
|
|
|
|
// We set the metatable now, even if the generator is invalid, so that it
|
|
// will be properly collected if it was invalid.
|
|
luaL_getmetatable(L, Gen);
|
|
lua_setmetatable(L, -2);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Returns a random numer, same interface as math.random.
|
|
*/
|
|
static int intf_random(lua_State *L)
|
|
{
|
|
if (lua_isnoneornil(L, 1)) {
|
|
double r = double(randomness::generator->next_random());
|
|
double r_max = double(std::numeric_limits<uint32_t>::max());
|
|
lua_push(L, r / (r_max + 1));
|
|
return 1;
|
|
}
|
|
else {
|
|
int32_t min;
|
|
int32_t max;
|
|
if (lua_isnumber(L, 2)) {
|
|
min = lua_check<int32_t>(L, 1);
|
|
max = lua_check<int32_t>(L, 2);
|
|
}
|
|
else {
|
|
min = 1;
|
|
max = lua_check<int32_t>(L, 1);
|
|
}
|
|
if (min > max) {
|
|
return luaL_argerror(L, 1, "min > max");
|
|
}
|
|
lua_push(L, randomness::generator->get_random_int(min, max));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int intf_wml_matches_filter(lua_State* L)
|
|
{
|
|
config cfg = luaW_checkconfig(L, 1);
|
|
config filter = luaW_checkconfig(L, 2);
|
|
lua_pushboolean(L, cfg.matches(filter));
|
|
return 1;
|
|
}
|
|
|
|
// End Callback implementations
|
|
|
|
// Template which allows to push member functions to the lua kernel base into lua as C functions, using a shim
|
|
typedef int (lua_kernel_base::*member_callback)(lua_State *L);
|
|
|
|
template <member_callback method>
|
|
int dispatch(lua_State *L) {
|
|
return ((lua_kernel_base::get_lua_kernel<lua_kernel_base>(L)).*method)(L);
|
|
}
|
|
|
|
// Ctor, initialization
|
|
lua_kernel_base::lua_kernel_base()
|
|
: mState(luaL_newstate())
|
|
, cmd_log_()
|
|
{
|
|
get_lua_kernel_base_ptr(mState) = this;
|
|
lua_State *L = mState;
|
|
|
|
cmd_log_ << "Initializing " << my_name() << "...\n";
|
|
|
|
// Open safe libraries.
|
|
// Debug and OS are not, but most of their functions will be disabled below.
|
|
cmd_log_ << "Adding standard libs...\n";
|
|
|
|
static const luaL_Reg safe_libs[] {
|
|
{ "", luaopen_base },
|
|
{ "table", luaopen_table },
|
|
{ "string", luaopen_string },
|
|
{ "math", luaopen_math },
|
|
{ "coroutine", luaopen_coroutine },
|
|
{ "debug", luaopen_debug },
|
|
{ "os", luaopen_os },
|
|
{ "utf8", luaopen_utf8 }, // added in Lua 5.3
|
|
{ nullptr, nullptr }
|
|
};
|
|
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 || strcmp(function, "getinfo") == 0) continue; //traceback is needed for our error handler
|
|
lua_pushnil(L); //getinfo is needed for ilua strict mode
|
|
lua_setfield(L, -3, function);
|
|
}
|
|
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.
|
|
cmd_log_ << "Adding error handler...\n";
|
|
push_error_handler(L);
|
|
|
|
// Create the gettext metatable.
|
|
cmd_log_ << lua_common::register_gettext_metatable(L);
|
|
|
|
// Create the tstring metatable.
|
|
cmd_log_ << lua_common::register_tstring_metatable(L);
|
|
|
|
|
|
lua_settop(L, 0);
|
|
|
|
// Define the CPP_function metatable ( so we can override print to point to a C++ member function, add "show_dialog" for this kernel, etc. )
|
|
cmd_log_ << "Adding boost function proxy...\n";
|
|
|
|
lua_cpp::register_metatable(L);
|
|
|
|
// Add some callback from the wesnoth lib
|
|
cmd_log_ << "Registering basic wesnoth API...\n";
|
|
|
|
static luaL_Reg const callbacks[] {
|
|
{ "compare_versions", &intf_compare_versions },
|
|
{ "have_file", &lua_fileops::intf_have_file },
|
|
{ "read_file", &lua_fileops::intf_read_file },
|
|
{ "textdomain", &lua_common::intf_textdomain },
|
|
{ "tovconfig", &lua_common::intf_tovconfig },
|
|
{ "get_dialog_value", &lua_gui2::intf_get_dialog_value },
|
|
{ "set_dialog_active", &lua_gui2::intf_set_dialog_active },
|
|
{ "set_dialog_visible", &lua_gui2::intf_set_dialog_visible },
|
|
{ "add_dialog_tree_node", &lua_gui2::intf_add_dialog_tree_node },
|
|
{ "add_widget_definition", &lua_gui2::intf_add_widget_definition },
|
|
{ "set_dialog_callback", &lua_gui2::intf_set_dialog_callback },
|
|
{ "set_dialog_canvas", &lua_gui2::intf_set_dialog_canvas },
|
|
{ "set_dialog_focus", &lua_gui2::intf_set_dialog_focus },
|
|
{ "set_dialog_markup", &lua_gui2::intf_set_dialog_markup },
|
|
{ "set_dialog_value", &lua_gui2::intf_set_dialog_value },
|
|
{ "remove_dialog_item", &lua_gui2::intf_remove_dialog_item },
|
|
{ "dofile", &dispatch<&lua_kernel_base::intf_dofile> },
|
|
{ "require", &dispatch<&lua_kernel_base::intf_require> },
|
|
{ "show_dialog", &video_dispatch<lua_gui2::show_dialog> },
|
|
{ "show_menu", &video_dispatch<lua_gui2::show_menu> },
|
|
{ "show_message_dialog", &video_dispatch<lua_gui2::show_message_dialog> },
|
|
{ "show_popup_dialog", &video_dispatch<lua_gui2::show_popup_dialog> },
|
|
{ "show_story", &video_dispatch<lua_gui2::show_story> },
|
|
{ "show_message_box", &video_dispatch<lua_gui2::show_message_box> },
|
|
{ "show_lua_console", &dispatch<&lua_kernel_base::intf_show_lua_console> },
|
|
{ "compile_formula", &lua_formula_bridge::intf_compile_formula},
|
|
{ "eval_formula", &lua_formula_bridge::intf_eval_formula},
|
|
{ "name_generator", &intf_name_generator },
|
|
{ "random", &intf_random },
|
|
{ "wml_matches_filter", &intf_wml_matches_filter },
|
|
{ nullptr, nullptr }
|
|
};
|
|
|
|
lua_getglobal(L, "wesnoth");
|
|
if (!lua_istable(L,-1)) {
|
|
lua_newtable(L);
|
|
}
|
|
luaL_setfuncs(L, callbacks, 0);
|
|
//lua_cpp::set_functions(L, cpp_callbacks, 0);
|
|
lua_setglobal(L, "wesnoth");
|
|
|
|
// Override the print function
|
|
cmd_log_ << "Redirecting print function...\n";
|
|
|
|
lua_getglobal(L, "print");
|
|
lua_setglobal(L, "std_print"); //storing original impl as 'std_print'
|
|
lua_settop(L, 0); //clear stack, just to be sure
|
|
|
|
lua_pushcfunction(L, &dispatch<&lua_kernel_base::intf_print>);
|
|
lua_setglobal(L, "print");
|
|
|
|
cmd_log_ << "Initializing package repository...\n";
|
|
// Create the package table.
|
|
lua_getglobal(L, "wesnoth");
|
|
lua_newtable(L);
|
|
lua_setfield(L, -2, "package");
|
|
lua_pop(L, 1);
|
|
lua_settop(L, 0);
|
|
lua_pushstring(L, "lua/package.lua");
|
|
int res = intf_require(L);
|
|
if(res != 1) {
|
|
cmd_log_ << "Error: Failed to initialize package repository. Falling back to less flexible C++ implementation.\n";
|
|
}
|
|
|
|
// Get some callbacks for map locations
|
|
cmd_log_ << "Adding map table...\n";
|
|
|
|
static luaL_Reg const map_callbacks[] {
|
|
{ "get_direction", &lua_map_location::intf_get_direction },
|
|
{ "vector_sum", &lua_map_location::intf_vector_sum },
|
|
{ "vector_diff", &lua_map_location::intf_vector_diff },
|
|
{ "vector_negation", &lua_map_location::intf_vector_negation },
|
|
{ "rotate_right_around_center", &lua_map_location::intf_rotate_right_around_center },
|
|
{ "tiles_adjacent", &lua_map_location::intf_tiles_adjacent },
|
|
{ "get_adjacent_tiles", &lua_map_location::intf_get_adjacent_tiles },
|
|
{ "distance_between", &lua_map_location::intf_distance_between },
|
|
{ "get_in_basis_N_NE", &lua_map_location::intf_get_in_basis_N_NE },
|
|
{ "get_relative_dir", &lua_map_location::intf_get_relative_dir },
|
|
{ nullptr, nullptr }
|
|
};
|
|
|
|
// Create the map_location table.
|
|
lua_getglobal(L, "wesnoth");
|
|
lua_newtable(L);
|
|
luaL_setfuncs(L, map_callbacks, 0);
|
|
lua_setfield(L, -2, "map");
|
|
lua_pop(L, 1);
|
|
|
|
// Add mersenne twister rng wrapper
|
|
cmd_log_ << "Adding rng tables...\n";
|
|
lua_rng::load_tables(L);
|
|
|
|
cmd_log_ << "Adding name generator metatable...\n";
|
|
luaL_newmetatable(L, Gen);
|
|
static luaL_Reg const generator[] {
|
|
{ "__call", &impl_name_generator_call},
|
|
{ "__gc", &impl_name_generator_collect},
|
|
{ nullptr, nullptr}
|
|
};
|
|
luaL_setfuncs(L, generator, 0);
|
|
|
|
// Create formula bridge metatables
|
|
cmd_log_ << lua_formula_bridge::register_metatables(L);
|
|
|
|
// Loading ilua:
|
|
cmd_log_ << "Loading ilua...\n";
|
|
|
|
lua_settop(L, 0);
|
|
luaW_getglobal(L, "wesnoth", "require");
|
|
lua_pushstring(L, "lua/ilua.lua");
|
|
if(protected_call(1, 1)) {
|
|
//run "ilua.set_strict()"
|
|
lua_pushstring(L, "set_strict");
|
|
lua_gettable(L, -2);
|
|
if (!this->protected_call(0,0, std::bind(&lua_kernel_base::log_error, this, _1, _2))) {
|
|
cmd_log_ << "Failed to activate strict mode.\n";
|
|
} else {
|
|
cmd_log_ << "Activated strict mode.\n";
|
|
}
|
|
|
|
lua_setglobal(L, "ilua"); //save ilua table as a global
|
|
} else {
|
|
cmd_log_ << "Error: failed to load ilua.\n";
|
|
}
|
|
lua_settop(L, 0);
|
|
|
|
cmd_log_ << "Loading core...\n";
|
|
luaW_getglobal(L, "wesnoth", "require");
|
|
lua_pushstring(L, "lua/core.lua");
|
|
if(!protected_call(1, 1)) {
|
|
cmd_log_ << "Error: Failed to load core.\n";
|
|
}
|
|
lua_settop(L, 0);
|
|
}
|
|
|
|
lua_kernel_base::~lua_kernel_base()
|
|
{
|
|
for (const auto& pair : this->registered_widget_definitions_) {
|
|
gui2::remove_single_widget_definition(std::get<0>(pair), std::get<1>(pair));
|
|
}
|
|
lua_close(mState);
|
|
}
|
|
|
|
void lua_kernel_base::log_error(char const * msg, char const * context)
|
|
{
|
|
ERR_LUA << context << ": " << msg;
|
|
}
|
|
|
|
void lua_kernel_base::throw_exception(char const * msg, char const * context)
|
|
{
|
|
throw game::lua_error(msg, context);
|
|
}
|
|
|
|
bool lua_kernel_base::protected_call(int nArgs, int nRets)
|
|
{
|
|
error_handler eh = std::bind(&lua_kernel_base::log_error, this, _1, _2 );
|
|
return this->protected_call(nArgs, nRets, eh);
|
|
}
|
|
|
|
bool lua_kernel_base::load_string(char const * prog)
|
|
{
|
|
error_handler eh = std::bind(&lua_kernel_base::log_error, this, _1, _2 );
|
|
return this->load_string(prog, eh);
|
|
}
|
|
|
|
bool lua_kernel_base::protected_call(int nArgs, int nRets, error_handler e_h)
|
|
{
|
|
return this->protected_call(mState, nArgs, nRets, e_h);
|
|
}
|
|
|
|
bool lua_kernel_base::protected_call(lua_State * L, int nArgs, int nRets, error_handler e_h)
|
|
{
|
|
int errcode = luaW_pcall_internal(L, nArgs, nRets);
|
|
|
|
if (errcode != LUA_OK) {
|
|
char const * msg = lua_tostring(L, -1);
|
|
|
|
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";
|
|
}
|
|
context += msg ? msg : "null string";
|
|
|
|
lua_pop(L, 1);
|
|
|
|
e_h(context.c_str(), "Lua Error");
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool lua_kernel_base::load_string(char const * prog, error_handler e_h)
|
|
{
|
|
int errcode = luaL_loadstring(mState, prog);
|
|
if (errcode != LUA_OK) {
|
|
char const * msg = lua_tostring(mState, -1);
|
|
std::string message = msg ? msg : "null string";
|
|
|
|
std::string context = "When parsing a string to lua, ";
|
|
|
|
if (errcode == LUA_ERRSYNTAX) {
|
|
context += " a syntax error";
|
|
} else if(errcode == LUA_ERRMEM){
|
|
context += " a memory error";
|
|
} else if(errcode == LUA_ERRGCMM) {
|
|
context += " an error in garbage collection metamethod";
|
|
} else {
|
|
context += " an unknown error";
|
|
}
|
|
|
|
lua_pop(mState, 1);
|
|
|
|
e_h(message.c_str(), context.c_str());
|
|
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void lua_kernel_base::run_lua_tag(const config& cfg)
|
|
{
|
|
int nArgs = 0;
|
|
if (const config& args = cfg.child("args")) {
|
|
luaW_pushconfig(this->mState, args);
|
|
++nArgs;
|
|
}
|
|
this->run(cfg["code"].str().c_str(), nArgs);
|
|
}
|
|
// Call load_string and protected call. Make them throw exceptions.
|
|
//
|
|
void lua_kernel_base::throwing_run(const char * prog, int nArgs)
|
|
{
|
|
cmd_log_ << "$ " << prog << "\n";
|
|
error_handler eh = std::bind(&lua_kernel_base::throw_exception, this, _1, _2 );
|
|
this->load_string(prog, eh);
|
|
lua_insert(mState, -nArgs - 1);
|
|
this->protected_call(nArgs, 0, eh);
|
|
}
|
|
|
|
// Do a throwing run, but if we catch a lua_error, reformat it with signature for this function and log it.
|
|
void lua_kernel_base::run(const char * prog, int nArgs)
|
|
{
|
|
try {
|
|
this->throwing_run(prog, nArgs);
|
|
} catch (game::lua_error & e) {
|
|
cmd_log_ << e.what() << "\n";
|
|
lua_kernel_base::log_error(e.what(), "In function lua_kernel::run()");
|
|
}
|
|
}
|
|
|
|
// Tests if a program resolves to an expression, and pretty prints it if it is, otherwise it runs it normally. Throws exceptions.
|
|
void lua_kernel_base::interactive_run(char const * prog) {
|
|
std::string experiment = "ilua._pretty_print(";
|
|
experiment += prog;
|
|
experiment += ")";
|
|
|
|
error_handler eh = std::bind(&lua_kernel_base::throw_exception, this, _1, _2 );
|
|
|
|
try {
|
|
// Try to load the experiment without syntax errors
|
|
this->load_string(experiment.c_str(), eh);
|
|
} catch (game::lua_error &) {
|
|
this->throwing_run(prog, 0); // Since it failed, fall back to the usual throwing_run, on the original input.
|
|
return;
|
|
}
|
|
// experiment succeeded, now run but log normally.
|
|
cmd_log_ << "$ " << prog << "\n";
|
|
this->protected_call(0, 0, eh);
|
|
}
|
|
/**
|
|
* Loads and executes a Lua file.
|
|
* - Arg 1: string containing the file name.
|
|
* - Ret *: values returned by executing the file body.
|
|
*/
|
|
int lua_kernel_base::intf_dofile(lua_State* L)
|
|
{
|
|
lua_rotate(L, 1, -1);
|
|
if (lua_fileops::load_file(L) != 1) return 0;
|
|
//^ should end with the file contents loaded on the stack. actually it will call lua_error otherwise, the return 0 is redundant.
|
|
|
|
error_handler eh = std::bind(&lua_kernel_base::log_error, this, _1, _2 );
|
|
lua_rotate(L, 1, 1);
|
|
this->protected_call(lua_gettop(L) - 1, LUA_MULTRET, eh);
|
|
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.
|
|
*/
|
|
int lua_kernel_base::intf_require(lua_State* L)
|
|
{
|
|
const char * m = luaL_checkstring(L, 1);
|
|
if(!m) {
|
|
return luaL_argerror(L, 1, "found a null string argument to wesnoth require");
|
|
}
|
|
|
|
// Check if there is already an entry.
|
|
|
|
lua_getglobal(L, "wesnoth");
|
|
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);
|
|
lua_pushvalue(L, 1);
|
|
// stack is now [packagename] [wesnoth] [package] [packagename]
|
|
|
|
if(lua_fileops::load_file(L) != 1) {
|
|
// should end with the file contents loaded on the stack. actually it will call lua_error otherwise, the return 0 is redundant.
|
|
// stack is now [packagename] [wesnoth] [package] [chunk]
|
|
return 0;
|
|
}
|
|
DBG_LUA << "require: loaded a file, now calling it\n";
|
|
|
|
if (!this->protected_call(L, 0, 1, std::bind(&lua_kernel_base::log_error, this, _1, _2))) {
|
|
// historically if wesnoth.require fails it just yields nil and some logging messages, not a lua error
|
|
return 0;
|
|
}
|
|
// stack is now [packagename] [wesnoth] [package] [results]
|
|
|
|
lua_pushvalue(L, 1);
|
|
lua_pushvalue(L, -2);
|
|
// stack is now [packagename] [wesnoth] [package] [results] [packagename] [results]
|
|
// Add the return value to the table.
|
|
|
|
lua_settable(L, -4);
|
|
// stack is now [packagename] [wesnoth] [package] [results]
|
|
return 1;
|
|
}
|
|
/**
|
|
* Loads the "package" package into the Lua environment.
|
|
* This action is inherently unsafe, as Lua scripts will now be able to
|
|
* load C libraries on their own, hence granting them the same privileges
|
|
* as the Wesnoth binary itsef.
|
|
*/
|
|
void lua_kernel_base::load_package()
|
|
{
|
|
lua_State *L = mState;
|
|
lua_pushcfunction(L, luaopen_package);
|
|
lua_pushstring(L, "package");
|
|
lua_call(L, 1, 0);
|
|
}
|
|
|
|
/**
|
|
* Gets all the global variable names in the Lua environment. This is useful for tab completion.
|
|
*/
|
|
std::vector<std::string> lua_kernel_base::get_global_var_names()
|
|
{
|
|
std::vector<std::string> ret;
|
|
|
|
lua_State *L = mState;
|
|
|
|
int idx = lua_gettop(L);
|
|
lua_getglobal(L, "_G");
|
|
lua_pushnil(L);
|
|
|
|
while (lua_next(L, idx+1) != 0) {
|
|
if (lua_isstring(L, -2)) {
|
|
ret.push_back(lua_tostring(L,-2));
|
|
}
|
|
lua_pop(L,1);
|
|
}
|
|
lua_settop(L, idx);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Gets all attribute names of an extended variable name. This is useful for tab completion.
|
|
*/
|
|
std::vector<std::string> lua_kernel_base::get_attribute_names(const std::string & input)
|
|
{
|
|
std::vector<std::string> ret;
|
|
std::string base_path = input;
|
|
size_t last_dot = base_path.find_last_of('.');
|
|
std::string partial_name = base_path.substr(last_dot + 1);
|
|
base_path.erase(last_dot);
|
|
std::string load = "return " + base_path;
|
|
|
|
lua_State* L = mState;
|
|
int save_stack = lua_gettop(L);
|
|
int result = luaL_loadstring(L, load.c_str());
|
|
if(result != LUA_OK) {
|
|
// This isn't at error level because it's a really low priority error; it just means the user tried to tab-complete something that doesn't exist.
|
|
LOG_LUA << "Error when attempting tab completion:\n";
|
|
LOG_LUA << luaL_checkstring(L, -1) << '\n';
|
|
// Just return an empty list; no matches were found
|
|
lua_settop(L, save_stack);
|
|
return ret;
|
|
}
|
|
|
|
luaW_pcall(L, 0, 1);
|
|
if(lua_istable(L, -1) || lua_isuserdata(L, -1)) {
|
|
int top = lua_gettop(L);
|
|
int obj = lua_absindex(L, -1);
|
|
if(luaL_getmetafield(L, obj, "__tab_enum") == LUA_TFUNCTION) {
|
|
lua_pushvalue(L, obj);
|
|
lua_pushlstring(L, partial_name.c_str(), partial_name.size());
|
|
luaW_pcall(L, 2, 1);
|
|
ret = lua_check<std::vector<std::string>>(L, -1);
|
|
} else {
|
|
lua_settop(L, top);
|
|
// Metafunction not found, so use lua_next to enumerate the table
|
|
for(lua_pushnil(L); lua_next(L, obj); lua_pop(L, 1)) {
|
|
if(lua_isstring(L, -2)) {
|
|
std::string attr = lua_tostring(L, -2);
|
|
if(attr.empty()) {
|
|
continue;
|
|
}
|
|
if(!isalpha(attr[0]) && attr[0] != '_') {
|
|
continue;
|
|
}
|
|
if(std::any_of(attr.begin(), attr.end(), [](char c){
|
|
return !isalpha(c) && !isdigit(c) && c != '_';
|
|
})) {
|
|
continue;
|
|
}
|
|
if(attr.substr(0, partial_name.size()) == partial_name) {
|
|
ret.push_back(base_path + "." + attr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
lua_settop(L, save_stack);
|
|
return ret;
|
|
}
|
|
|
|
lua_kernel_base*& lua_kernel_base::get_lua_kernel_base_ptr(lua_State *L)
|
|
{
|
|
#ifdef __GNUC__
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
|
#endif
|
|
return *reinterpret_cast<lua_kernel_base**>(lua_getextraspace(L));
|
|
#ifdef __GNUC__
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
}
|
|
|
|
uint32_t lua_kernel_base::get_random_seed()
|
|
{
|
|
return seed_rng::next_seed();
|
|
}
|