Split out the main core modules of Wesnoth (wml, gui, stringx) into separate C++ source files
And load them with lua_requiref because we can.
This commit is contained in:
parent
3848f31484
commit
1323e75ee2
11 changed files with 757 additions and 602 deletions
|
@ -2672,6 +2672,20 @@
|
|||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|Win32'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)Scripting\</ObjectFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\scripting\lua_wml.cpp">
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|Win32'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)Scripting\</ObjectFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\scripting\lua_stringx.cpp">
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|Win32'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)Scripting\</ObjectFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\scripting\lua_kernel_base.cpp">
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)Scripting\</ObjectFileName>
|
||||
|
@ -3956,6 +3970,8 @@
|
|||
<ClInclude Include="..\..\src\scripting\lua_fileops.hpp" />
|
||||
<ClInclude Include="..\..\src\scripting\lua_formula_bridge.hpp" />
|
||||
<ClInclude Include="..\..\src\scripting\lua_gui2.hpp" />
|
||||
<ClInclude Include="..\..\src\scripting\lua_wml.hpp" />
|
||||
<ClInclude Include="..\..\src\scripting\lua_stringx.hpp" />
|
||||
<ClInclude Include="..\..\src\scripting\lua_kernel_base.hpp" />
|
||||
<ClInclude Include="..\..\src\scripting\lua_map_location_ops.hpp" />
|
||||
<ClInclude Include="..\..\src\scripting\lua_pathfind_cost_calculator.hpp" />
|
||||
|
|
|
@ -2678,6 +2678,20 @@
|
|||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|x64'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|x64'">$(IntDir)Scripting\</ObjectFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\scripting\lua_wml.cpp">
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|x64'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|x64'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|x64'">$(IntDir)Scripting\</ObjectFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\scripting\lua_stringx.cpp">
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|x64'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|x64'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|x64'">$(IntDir)Scripting\</ObjectFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\scripting\lua_kernel_base.cpp">
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)Scripting\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|x64'">$(IntDir)Scripting\</ObjectFileName>
|
||||
|
@ -3981,6 +3995,8 @@
|
|||
<ClInclude Include="..\..\src\scripting\lua_fileops.hpp" />
|
||||
<ClInclude Include="..\..\src\scripting\lua_formula_bridge.hpp" />
|
||||
<ClInclude Include="..\..\src\scripting\lua_gui2.hpp" />
|
||||
<ClInclude Include="..\..\src\scripting\lua_wml.hpp" />
|
||||
<ClInclude Include="..\..\src\scripting\lua_stringx.hpp" />
|
||||
<ClInclude Include="..\..\src\scripting\lua_kernel_base.hpp" />
|
||||
<ClInclude Include="..\..\src\scripting\lua_map_location_ops.hpp" />
|
||||
<ClInclude Include="..\..\src\scripting\lua_pathfind_cost_calculator.hpp" />
|
||||
|
|
|
@ -315,6 +315,8 @@ scripting/lua_cpp_function.cpp
|
|||
scripting/lua_fileops.cpp
|
||||
scripting/lua_formula_bridge.cpp
|
||||
scripting/lua_gui2.cpp
|
||||
scripting/lua_wml.cpp
|
||||
scripting/lua_stringx.cpp
|
||||
scripting/lua_kernel_base.cpp
|
||||
scripting/lua_map_location_ops.cpp
|
||||
scripting/lua_preferences.cpp
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
#include "config.hpp"
|
||||
#include "log.hpp"
|
||||
#include "scripting/lua_common.hpp"
|
||||
#include "scripting/lua_cpp_function.hpp"
|
||||
#include "scripting/lua_kernel_base.hpp"
|
||||
#include "scripting/lua_unit.hpp"
|
||||
#include "scripting/lua_unit_type.hpp"
|
||||
|
@ -966,4 +967,27 @@ int intf_add_widget_definition(lua_State* L)
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int luaW_open(lua_State* L) {
|
||||
auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
|
||||
lk.add_log("Adding gui module...\n");
|
||||
static luaL_Reg const gui_callbacks[] = {
|
||||
{ "show_menu", &show_menu },
|
||||
{ "show_narration", &show_message_dialog },
|
||||
{ "show_popup", &show_popup_dialog },
|
||||
{ "show_story", &show_story },
|
||||
{ "show_prompt", &show_message_box },
|
||||
{ "add_widget_definition", &intf_add_widget_definition },
|
||||
{ nullptr, nullptr },
|
||||
};
|
||||
std::vector<lua_cpp::Reg> const cpp_gui_callbacks {
|
||||
{"show_lua_console", std::bind(&lua_kernel_base::intf_show_lua_console, &lk, _1)},
|
||||
{nullptr, nullptr}
|
||||
};
|
||||
lua_newtable(L);
|
||||
luaL_setfuncs(L, gui_callbacks, 0);
|
||||
lua_cpp::set_functions(L, cpp_gui_callbacks);
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // end namespace lua_gui2
|
||||
|
|
|
@ -42,5 +42,6 @@ int show_message_box(lua_State* L);
|
|||
int show_lua_console(lua_State*L, lua_kernel_base * lk);
|
||||
int show_gamestate_inspector(const vconfig& cfg, const game_data& data, const game_state& state);
|
||||
int intf_remove_dialog_item(lua_State *L);
|
||||
int luaW_open(lua_State *L);
|
||||
|
||||
} // end namespace lua_gui2
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
#include "game_config.hpp"
|
||||
#include "game_errors.hpp"
|
||||
#include "gui/core/gui_definition.hpp"
|
||||
#include "gui/core/gui_definition.hpp" // for remove_single_widget_definition
|
||||
#include "log.hpp"
|
||||
#include "lua_jailbreak_exception.hpp" // for lua_jailbreak_exception
|
||||
#include "random.hpp"
|
||||
|
@ -33,6 +33,8 @@
|
|||
#include "scripting/lua_fileops.hpp"
|
||||
#include "scripting/lua_formula_bridge.hpp"
|
||||
#include "scripting/lua_gui2.hpp"
|
||||
#include "scripting/lua_wml.hpp"
|
||||
#include "scripting/lua_stringx.hpp"
|
||||
#include "scripting/lua_map_location_ops.hpp"
|
||||
#include "scripting/lua_rng.hpp"
|
||||
#include "scripting/push_check.hpp"
|
||||
|
@ -40,16 +42,10 @@
|
|||
#include "game_version.hpp" // for do_version_check, etc
|
||||
#include "picture.hpp"
|
||||
|
||||
#include "formula/string_utils.hpp"
|
||||
#include "serialization/string_utils.hpp"
|
||||
#include "serialization/schema_validator.hpp"
|
||||
#include "serialization/parser.hpp"
|
||||
#include "serialization/preprocessor.hpp"
|
||||
#include "utils/functional.hpp"
|
||||
#include "utils/name_generator.hpp"
|
||||
#include "utils/markov_generator.hpp"
|
||||
#include "utils/context_free_grammar_generator.hpp"
|
||||
#include "variable.hpp" // for config_variable_set
|
||||
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
|
@ -57,8 +53,6 @@
|
|||
#include <string>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
|
||||
#include "lua/lauxlib.h"
|
||||
#include "lua/lua.h"
|
||||
|
@ -288,100 +282,6 @@ static int intf_random(lua_State *L)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a WML table matches a filter
|
||||
* Arg 1: table to test
|
||||
* Arg 2: filter
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two WML tables
|
||||
* Arg 1: base table
|
||||
* Arg 2: table to merge in
|
||||
*/
|
||||
static int intf_wml_merge(lua_State* L)
|
||||
{
|
||||
config base = luaW_checkconfig(L, 1);
|
||||
config merge = luaW_checkconfig(L, 2);
|
||||
const std::string mode = lua_isstring(L, 3) ? luaL_checkstring(L, 3) : "merge";
|
||||
if(mode == "append") {
|
||||
base.merge_attributes(merge);
|
||||
base.append_children(merge);
|
||||
} else {
|
||||
if(mode == "replace") {
|
||||
for(const auto& c : merge.all_children_range()) {
|
||||
base.clear_children(c.key);
|
||||
}
|
||||
} else if(mode != "merge") {
|
||||
return luaL_argerror(L, 3, "invalid merge mode - must be merge, append, or replace");
|
||||
}
|
||||
base.merge_with(merge);
|
||||
}
|
||||
luaW_pushconfig(L, base);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a diff of two WML tables
|
||||
* Arg 1: left table
|
||||
* Arg 2: right table
|
||||
*/
|
||||
static int intf_wml_diff(lua_State* L)
|
||||
{
|
||||
config lhs = luaW_checkconfig(L, 1);
|
||||
config rhs = luaW_checkconfig(L, 2);
|
||||
luaW_pushconfig(L, lhs.get_diff(rhs));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a diff to a WML table
|
||||
* Arg 1: base table
|
||||
* Arg 2: WML diff
|
||||
*/
|
||||
static int intf_wml_patch(lua_State* L)
|
||||
{
|
||||
config base = luaW_checkconfig(L, 1);
|
||||
config patch = luaW_checkconfig(L, 2);
|
||||
base.apply_diff(patch);
|
||||
luaW_pushconfig(L, base);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if two WML tables are equal (have the same keys and values, same tags, recursively)
|
||||
* Arg 1: left table
|
||||
* Arg 2: right table
|
||||
*/
|
||||
static int intf_wml_equal(lua_State* L)
|
||||
{
|
||||
config left = luaW_checkconfig(L, 1);
|
||||
config right = luaW_checkconfig(L, 2);
|
||||
lua_pushboolean(L, left == right);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a table represents a valid WML table
|
||||
* Arg 1: table
|
||||
*/
|
||||
static int intf_wml_valid(lua_State* L)
|
||||
{
|
||||
config test;
|
||||
if(luaW_toconfig(L, 1, test)) {
|
||||
// The validate_wml call is PROBABLY redundant, but included just in case validation changes and toconfig isn't updated to match
|
||||
lua_pushboolean(L, test.validate_wml());
|
||||
} else lua_pushboolean(L, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message
|
||||
* Arg 1: (optional) Logger
|
||||
|
@ -454,432 +354,12 @@ static int intf_get_time_stamp(lua_State *L) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a message by interpolating WML variable syntax
|
||||
* Arg 1: (optional) Logger
|
||||
* Arg 2: Message
|
||||
*/
|
||||
static int intf_format(lua_State* L)
|
||||
{
|
||||
config cfg = luaW_checkconfig(L, 2);
|
||||
config_variable_set variables(cfg);
|
||||
if(lua_isstring(L, 1)) {
|
||||
std::string str = lua_tostring(L, 1);
|
||||
lua_push(L, utils::interpolate_variables_into_string(str, variables));
|
||||
return 1;
|
||||
}
|
||||
t_string str = luaW_checktstring(L, 1);
|
||||
lua_push(L, utils::interpolate_variables_into_tstring(str, variables));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a list into human-readable format
|
||||
* Arg 1: default value, used if the list is empty
|
||||
* Arg 2: list of strings
|
||||
*/
|
||||
template<bool conjunct>
|
||||
static int intf_format_list(lua_State* L)
|
||||
{
|
||||
const t_string empty = luaW_checktstring(L, 1);
|
||||
auto values = lua_check<std::vector<t_string>>(L, 2);
|
||||
lua_push(L, (conjunct ? utils::format_conjunct_list : utils::format_disjunct_list)(empty, values));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables indexing a string by an integer, while also treating the stringx module as its metatable.__index
|
||||
*/
|
||||
static int impl_str_index(lua_State* L)
|
||||
{
|
||||
if(lua_type(L, 2) == LUA_TSTRING) {
|
||||
// return stringx[key]
|
||||
lua_getglobal(L, "stringx");
|
||||
lua_pushvalue(L, 2);
|
||||
lua_gettable(L, -2);
|
||||
return 1;
|
||||
} else if(lua_type(L, 2) == LUA_TNUMBER) {
|
||||
// return string.sub(str, key, key)
|
||||
luaW_getglobal(L, "string", "sub");
|
||||
lua_pushvalue(L, 1);
|
||||
lua_pushvalue(L, 2);
|
||||
lua_pushvalue(L, 2);
|
||||
lua_call(L, 3, 1);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a string into parts according to options
|
||||
* Arg 1: String to split
|
||||
* Arg 2: Separator
|
||||
* Arg 3: Options table
|
||||
*/
|
||||
static int intf_str_split(lua_State* L)
|
||||
{
|
||||
enum {BASIC, ESCAPED, PAREN, ANIM} type = BASIC;
|
||||
const std::string& str = luaL_checkstring(L, 1);
|
||||
const std::string& sep = luaL_optstring(L, 2, ",");
|
||||
std::string left, right;
|
||||
int flags = utils::REMOVE_EMPTY | utils::STRIP_SPACES;
|
||||
if(lua_istable(L, 3)) {
|
||||
flags = 0;
|
||||
if(luaW_table_get_def(L, 3, "remove_empty", true)) {
|
||||
flags |= utils::REMOVE_EMPTY;
|
||||
}
|
||||
if(luaW_table_get_def(L, 3, "strip_spaces", true)) {
|
||||
flags |= utils::STRIP_SPACES;
|
||||
}
|
||||
bool anim = luaW_table_get_def(L, 3, "expand_anim", false);
|
||||
if(luaW_tableget(L, 3, "escape")) {
|
||||
if(anim) {
|
||||
return luaL_error(L, "escape and expand_anim options are incompatible!");
|
||||
}
|
||||
type = ESCAPED;
|
||||
left = luaL_checkstring(L, -1);
|
||||
if(left.size() != 1) {
|
||||
return luaL_error(L, "escape must be a single character");
|
||||
}
|
||||
} else if(luaW_tableget(L, 3, "quote")) {
|
||||
left = right = luaL_checkstring(L, -1);
|
||||
if(anim) {
|
||||
type = ANIM;
|
||||
left.push_back('[');
|
||||
right.push_back(']');
|
||||
} else type = PAREN;
|
||||
} else if(luaW_tableget(L, 3, "quote_left") && luaW_tableget(L, 3, "quote_right")) {
|
||||
left = luaL_checkstring(L, -2);
|
||||
right = luaL_checkstring(L, -1);
|
||||
if(anim) {
|
||||
if(left.find_first_of("[]") != std::string::npos || right.find_first_of("[]") != std::string::npos) {
|
||||
return luaL_error(L, "left and right cannot include square brackets [] if expand_anim is enabled");
|
||||
}
|
||||
type = ANIM;
|
||||
left.push_back('[');
|
||||
right.push_back(']');
|
||||
} else type = PAREN;
|
||||
} else if(anim) {
|
||||
type = ANIM;
|
||||
left = "([";
|
||||
right = ")]";
|
||||
}
|
||||
if(type != ESCAPED && left.size() != right.size()) {
|
||||
return luaL_error(L, "left and right need to be strings of the same length");
|
||||
}
|
||||
}
|
||||
switch(type) {
|
||||
case BASIC:
|
||||
lua_push(L, utils::split(str, sep[0], flags));
|
||||
break;
|
||||
case ESCAPED:
|
||||
lua_push(L, utils::quoted_split(str, sep[0], flags, left[0]));
|
||||
break;
|
||||
case PAREN:
|
||||
lua_push(L, utils::parenthetical_split(str, sep[0], left, right, flags));
|
||||
break;
|
||||
case ANIM:
|
||||
lua_push(L, utils::square_parenthetical_split(str, sep[0], left, right, flags));
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a string into parenthesized portions and portions between parenthesized portions
|
||||
* Arg 1: String to split
|
||||
* Arg 2: Possible left parentheses
|
||||
* Arg 3: Matching right parentheses
|
||||
*/
|
||||
static int intf_str_paren_split(lua_State* L)
|
||||
{
|
||||
const std::string& str = luaL_checkstring(L, 1);
|
||||
const std::string& left = luaL_optstring(L, 2, "(");
|
||||
const std::string& right = luaL_optstring(L, 3, ")");
|
||||
if(left.size() != right.size()) {
|
||||
return luaL_error(L, "left and right need to be strings of the same length");
|
||||
}
|
||||
bool strip_spaces = luaL_opt(L, luaW_toboolean, 4, true);
|
||||
lua_push(L, utils::parenthetical_split(str, 0, left, right, strip_spaces ? utils::STRIP_SPACES : 0));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a string into a map
|
||||
* Arg 1: string to split
|
||||
* Arg 2: Separator for items
|
||||
* Arg 3: Separator for key and value
|
||||
*/
|
||||
static int intf_str_map_split(lua_State* L)
|
||||
{
|
||||
const std::string& str = luaL_checkstring(L, 1);
|
||||
const std::string& sep = luaL_optstring(L, 2, ",");
|
||||
const std::string& kv = luaL_optstring(L, 3, ":");
|
||||
std::string dflt;
|
||||
if(sep.size() != 1) {
|
||||
return luaL_error(L, "separator must be a single character");
|
||||
}
|
||||
if(kv.size() != 1) {
|
||||
return luaL_error(L, "key_value_separator must be a single character");
|
||||
}
|
||||
int flags = utils::REMOVE_EMPTY | utils::STRIP_SPACES;
|
||||
if(lua_istable(L, 4)) {
|
||||
flags = 0;
|
||||
if(luaW_table_get_def(L, 4, "remove_empty", true)) {
|
||||
flags |= utils::REMOVE_EMPTY;
|
||||
}
|
||||
if(luaW_table_get_def(L, 4, "strip_spaces", true)) {
|
||||
flags |= utils::STRIP_SPACES;
|
||||
}
|
||||
if(luaW_tableget(L, 4, "default")) {
|
||||
dflt = luaL_checkstring(L, -1);
|
||||
}
|
||||
}
|
||||
lua_push(L, utils::map_split(str, sep[0], kv[0], flags, dflt));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins a list into a string; calls __tostring and __index metamethods
|
||||
* Arg 1: list to join
|
||||
* Arg 2: separator
|
||||
* (arguments can be swapped)
|
||||
*/
|
||||
static int intf_str_join(lua_State* L) {
|
||||
// Support both join(list, [sep]) and join(sep, list)
|
||||
// The latter form means sep:join(list) also works.
|
||||
std::string sep;
|
||||
int list_idx;
|
||||
if(lua_istable(L, 1)) {
|
||||
list_idx = 1;
|
||||
sep = luaL_optstring(L, 2, ",");
|
||||
} else if(lua_istable(L, 2)) {
|
||||
sep = luaL_checkstring(L, 1);
|
||||
list_idx = 2;
|
||||
} else return luaL_error(L, "invalid arguments to join, should have map and separator");
|
||||
std::vector<std::string> pieces;
|
||||
for(int i = 1; i <= luaL_len(L, list_idx); i++) {
|
||||
lua_getglobal(L, "tostring");
|
||||
lua_geti(L, list_idx, i);
|
||||
lua_call(L, 1, 1);
|
||||
pieces.push_back(luaL_checkstring(L, -1));
|
||||
}
|
||||
lua_push(L, utils::join(pieces, sep));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins a map into a string; calls __tostring metamethods (on both key and value) but not __index
|
||||
* Arg 1: list to join
|
||||
* Arg 2: separator for items
|
||||
* Arg 3: separator for key and value
|
||||
* (list argument can be swapped to any position)
|
||||
*/
|
||||
static int intf_str_join_map(lua_State* L) {
|
||||
// Support join_map(map, [sep], [kv_sep]), join_map(sep, map, [kv_sep]), and join_map(sep, kv_sep, map)
|
||||
// The latter forms mean sep:join_map(kv_sep, map) and sep:join_map(map) also work.
|
||||
// If only one separator is given in the first form, it will be sep, not kv_sep
|
||||
std::string sep, kv;
|
||||
int map_idx;
|
||||
if(lua_istable(L, 1)) {
|
||||
map_idx = 1;
|
||||
sep = luaL_optstring(L, 2, ",");
|
||||
kv = luaL_optstring(L, 3, ":");
|
||||
} else if(lua_istable(L, 2)) {
|
||||
sep = luaL_checkstring(L, 1);
|
||||
map_idx = 2;
|
||||
kv = luaL_optstring(L, 3, ":");
|
||||
} else if(lua_istable(L, 3)) {
|
||||
sep = luaL_checkstring(L, 1);
|
||||
kv = luaL_checkstring(L, 2);
|
||||
map_idx = 3;
|
||||
} else return luaL_error(L, "invalid arguments to join_map, should have map, separator, and key_value_separator");
|
||||
std::map<std::string, std::string> pieces;
|
||||
for(lua_pushnil(L); lua_next(L, map_idx); /*pop in loop body*/) {
|
||||
int key_idx = lua_absindex(L, -2), val_idx = lua_absindex(L, -1);
|
||||
lua_getglobal(L, "tostring");
|
||||
lua_pushvalue(L, key_idx);
|
||||
lua_call(L, 1, 1);
|
||||
std::string& val = pieces[luaL_checkstring(L, -1)];
|
||||
lua_getglobal(L, "tostring");
|
||||
lua_pushvalue(L, val_idx);
|
||||
lua_call(L, 1, 1);
|
||||
val = luaL_checkstring(L, -1);
|
||||
lua_settop(L, key_idx);
|
||||
}
|
||||
lua_push(L, utils::join_map(pieces, sep, kv));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims whitespace from the beginning and end of a string
|
||||
*/
|
||||
static int intf_str_trim(lua_State* L)
|
||||
{
|
||||
std::string str = luaL_checkstring(L, 1);
|
||||
boost::trim(str);
|
||||
lua_pushlstring(L, str.c_str(), str.size());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Override string.format to coerce the format to a string
|
||||
static int intf_str_format(lua_State* L)
|
||||
{
|
||||
int nargs = lua_gettop(L);
|
||||
if(luaW_iststring(L, 1)) {
|
||||
// get the tostring() function and call it on the first argument
|
||||
lua_getglobal(L, "tostring");
|
||||
lua_pushvalue(L, 1);
|
||||
lua_call(L, 1, 1);
|
||||
// replace the first argument with the coerced value
|
||||
lua_replace(L, 1);
|
||||
}
|
||||
// grab the original string.format function from the closure...
|
||||
lua_pushvalue(L, lua_upvalueindex(1));
|
||||
// ...move it to the bottom of the stack...
|
||||
lua_insert(L, 1);
|
||||
// ...and finally pass along all the arguments to it.
|
||||
lua_call(L, nargs, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a range string of the form a-b into an interval pair
|
||||
* Accepts the string "infinity" as representing a Very Large Number
|
||||
*/
|
||||
static int intf_parse_range(lua_State* L)
|
||||
{
|
||||
const std::string str = luaL_checkstring(L, 1);
|
||||
auto interval = utils::parse_range(str);
|
||||
lua_pushnumber(L, interval.first);
|
||||
lua_pushnumber(L, interval.second);
|
||||
return 2;
|
||||
}
|
||||
|
||||
static int intf_get_language(lua_State* L)
|
||||
{
|
||||
lua_push(L, get_language().localename);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps a wml table or userdata wml object into a pretty string.
|
||||
* - Arg 1: wml table or vconfig userdata
|
||||
* - Ret 1: string
|
||||
*/
|
||||
static int intf_wml_tostring(lua_State* L) {
|
||||
const config& arg = luaW_checkconfig(L, 1);
|
||||
std::ostringstream stream;
|
||||
write(stream, arg);
|
||||
lua_pushstring(L, stream.str().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a WML file into a config
|
||||
* - Arg 1: WML file path
|
||||
* - Arg 2: (optional) Array of preprocessor defines, or false to skip preprocessing (true is also valid)
|
||||
* - Arg 3: (optional) Path to a schema file for validation (omit for no validation)
|
||||
* - Ret: config
|
||||
*/
|
||||
static int intf_load_wml(lua_State* L)
|
||||
{
|
||||
std::string file = luaL_checkstring(L, 1);
|
||||
bool preprocess = true;
|
||||
preproc_map defines_map;
|
||||
if(lua_type(L, 2) == LUA_TBOOLEAN) {
|
||||
preprocess = luaW_toboolean(L, 2);
|
||||
} else if(lua_type(L, 2) == LUA_TTABLE || lua_type(L, 2) == LUA_TUSERDATA) {
|
||||
lua_len(L, 2);
|
||||
int n = lua_tonumber(L, -1);
|
||||
lua_pop(L, 1);
|
||||
for(int i = 0; i < n; i++) {
|
||||
lua_geti(L, 2, i);
|
||||
if(!lua_isstring(L, -1)) {
|
||||
return luaL_argerror(L, 2, "expected bool or array of strings");
|
||||
}
|
||||
std::string define = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
if(!define.empty()) {
|
||||
defines_map.emplace(define, preproc_define(define));
|
||||
}
|
||||
}
|
||||
} else if(!lua_isnoneornil(L, 2)) {
|
||||
return luaL_argerror(L, 2, "expected bool or array of strings");
|
||||
}
|
||||
std::string schema_path = luaL_optstring(L, 3, "");
|
||||
std::shared_ptr<schema_validation::schema_validator> validator;
|
||||
if(!schema_path.empty()) {
|
||||
validator.reset(new schema_validation::schema_validator(filesystem::get_wml_location(schema_path)));
|
||||
validator->set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
|
||||
}
|
||||
std::string wml_file = filesystem::get_wml_location(file);
|
||||
filesystem::scoped_istream stream;
|
||||
config result;
|
||||
if(preprocess) {
|
||||
stream = preprocess_file(wml_file, &defines_map);
|
||||
} else {
|
||||
stream.reset(new std::ifstream(wml_file));
|
||||
}
|
||||
read(result, *stream, validator.get());
|
||||
luaW_pushconfig(L, result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a WML string into a config; does not preprocess or validate
|
||||
* - Arg 1: WML string
|
||||
* - Ret: config
|
||||
*/
|
||||
static int intf_parse_wml(lua_State* L)
|
||||
{
|
||||
std::string wml = luaL_checkstring(L, 1);
|
||||
std::string schema_path = luaL_optstring(L, 2, "");
|
||||
std::shared_ptr<schema_validation::schema_validator> validator;
|
||||
if(!schema_path.empty()) {
|
||||
validator.reset(new schema_validation::schema_validator(filesystem::get_wml_location(schema_path)));
|
||||
validator->set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
|
||||
}
|
||||
config result;
|
||||
read(result, wml, validator.get());
|
||||
luaW_pushconfig(L, result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a clone (deep copy) of the passed config, which can be either a normal config or a vconfig
|
||||
* If it is a vconfig, the underlying config is also cloned.
|
||||
* - Arg 1: a config
|
||||
* - Ret: the cloned config
|
||||
*/
|
||||
static int intf_clone_wml(lua_State* L)
|
||||
{
|
||||
const vconfig* vcfg = nullptr;
|
||||
const config& cfg = luaW_checkconfig(L, 1, vcfg);
|
||||
if(vcfg) {
|
||||
config clone_underlying = vcfg->get_config();
|
||||
vconfig clone(clone_underlying);
|
||||
luaW_pushvconfig(L, clone);
|
||||
} else {
|
||||
luaW_pushconfig(L, cfg);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates variables into a WML table, including [insert_tag]
|
||||
* Arg 1: WML table to interpolate into
|
||||
* Arg 2: WML table of variables
|
||||
*/
|
||||
static int intf_wml_interpolate(lua_State* L)
|
||||
{
|
||||
config cfg = luaW_checkconfig(L, 1), vars_cfg = luaW_checkconfig(L, 2);
|
||||
config_variable_set vars(vars_cfg);
|
||||
vconfig vcfg(cfg, vars);
|
||||
luaW_pushconfig(L, vcfg.get_parsed_config());
|
||||
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
|
||||
|
@ -900,6 +380,12 @@ lua_kernel_base::lua_kernel_base()
|
|||
|
||||
cmd_log_ << "Initializing " << my_name() << "...\n";
|
||||
|
||||
// Define the CPP_function metatable ( so we can override print to point to a C++ member function, add "show_dialog" for this kernel, etc. )
|
||||
// Do it first of all in case C++ functions are ever used in the core Wesnoth libs loaded in the next step
|
||||
cmd_log_ << "Adding boost function proxy...\n";
|
||||
|
||||
lua_cpp::register_metatable(L);
|
||||
|
||||
// Open safe libraries.
|
||||
// Debug and OS are not, but most of their functions will be disabled below.
|
||||
cmd_log_ << "Adding standard libs...\n";
|
||||
|
@ -913,11 +399,15 @@ lua_kernel_base::lua_kernel_base()
|
|||
{ "debug", luaopen_debug },
|
||||
{ "os", luaopen_os },
|
||||
{ "utf8", luaopen_utf8 }, // added in Lua 5.3
|
||||
// Wesnoth libraries
|
||||
{ "stringx",lua_stringx::luaW_open },
|
||||
{ "wml", lua_wml::luaW_open },
|
||||
{ "gui", lua_gui2::luaW_open },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
for (luaL_Reg const *lib = safe_libs; lib->func; ++lib)
|
||||
{
|
||||
luaL_requiref(L, lib->name, lib->func, 1);
|
||||
luaL_requiref(L, lib->name, lib->func, true);
|
||||
lua_pop(L, 1); /* remove lib */
|
||||
}
|
||||
|
||||
|
@ -947,11 +437,6 @@ lua_kernel_base::lua_kernel_base()
|
|||
|
||||
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";
|
||||
|
||||
|
@ -995,83 +480,12 @@ lua_kernel_base::lua_kernel_base()
|
|||
luaL_setfuncs(L, callbacks, 0);
|
||||
//lua_cpp::set_functions(L, cpp_callbacks, 0);
|
||||
lua_setglobal(L, "wesnoth");
|
||||
|
||||
cmd_log_ << "Adding stringx module...\n";
|
||||
static luaL_Reg const str_callbacks[] = {
|
||||
{ "split", &intf_str_split },
|
||||
{ "parenthetical_split", &intf_str_paren_split },
|
||||
{ "map_split", &intf_str_map_split },
|
||||
{ "join", &intf_str_join },
|
||||
{ "join_map", &intf_str_join_map },
|
||||
{ "trim", &intf_str_trim },
|
||||
{ "parse_range", &intf_parse_range },
|
||||
{ "vformat", &intf_format },
|
||||
{ "format_conjunct_list", &intf_format_list<true> },
|
||||
{ "format_disjunct_list", &intf_format_list<false> },
|
||||
{ nullptr, nullptr },
|
||||
};
|
||||
lua_newtable(L);
|
||||
luaL_setfuncs(L, str_callbacks, 0);
|
||||
// Set the stringx metatable to index the string module
|
||||
lua_createtable(L, 0, 1);
|
||||
lua_getglobal(L, "string");
|
||||
lua_setfield(L, -2, "__index");
|
||||
lua_setmetatable(L, -2);
|
||||
lua_setglobal(L, "stringx");
|
||||
|
||||
// Set the metatable of strings to index the stringx module instead of the string module
|
||||
lua_pushliteral(L, "");
|
||||
lua_getmetatable(L, -1);
|
||||
lua_pushcfunction(L, &impl_str_index);
|
||||
lua_setfield(L, -2, "__index");
|
||||
lua_setmetatable(L, -2);
|
||||
|
||||
// Override string.format
|
||||
lua_getglobal(L, "string");
|
||||
lua_getfield(L, -1, "format");
|
||||
lua_pushcclosure(L, &intf_str_format, 1);
|
||||
lua_setfield(L, -2, "format");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Create the gettext metatable.
|
||||
cmd_log_ << lua_common::register_gettext_metatable(L);
|
||||
// Create the tstring metatable.
|
||||
cmd_log_ << lua_common::register_tstring_metatable(L);
|
||||
|
||||
cmd_log_ << "Adding wml module...\n";
|
||||
static luaL_Reg const wml_callbacks[]= {
|
||||
{ "load", &intf_load_wml},
|
||||
{ "parse", &intf_parse_wml},
|
||||
{ "clone", &intf_clone_wml},
|
||||
{ "merge", &intf_wml_merge},
|
||||
{ "diff", &intf_wml_diff},
|
||||
{ "patch", &intf_wml_patch},
|
||||
{ "equal", &intf_wml_equal},
|
||||
{ "valid", &intf_wml_valid},
|
||||
{ "matches_filter", &intf_wml_matches_filter},
|
||||
{ "tostring", &intf_wml_tostring},
|
||||
{ "interpolate", &intf_wml_interpolate},
|
||||
{ nullptr, nullptr },
|
||||
};
|
||||
lua_newtable(L);
|
||||
luaL_setfuncs(L, wml_callbacks, 0);
|
||||
lua_setglobal(L, "wml");
|
||||
|
||||
cmd_log_ << "Adding gui module...\n";
|
||||
static luaL_Reg const gui_callbacks[]= {
|
||||
{ "show_menu", &lua_gui2::show_menu },
|
||||
{ "show_narration", &lua_gui2::show_message_dialog },
|
||||
{ "show_popup", &lua_gui2::show_popup_dialog },
|
||||
{ "show_story", &lua_gui2::show_story },
|
||||
{ "show_prompt", &lua_gui2::show_message_box },
|
||||
{ "show_lua_console", &dispatch<&lua_kernel_base::intf_show_lua_console> },
|
||||
{ "add_widget_definition", &lua_gui2::intf_add_widget_definition },
|
||||
{ nullptr, nullptr },
|
||||
};
|
||||
lua_newtable(L);
|
||||
luaL_setfuncs(L, gui_callbacks, 0);
|
||||
lua_setglobal(L, "gui");
|
||||
|
||||
// Override the print function
|
||||
cmd_log_ << "Redirecting print function...\n";
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ public:
|
|||
|
||||
/** Access / manipulate logging of lua kernel activities */
|
||||
const std::stringstream & get_log() { cmd_log_.log_ << std::flush; return cmd_log_.log_; }
|
||||
void add_log(const char* str) { cmd_log_ << str; }
|
||||
void clear_log() { cmd_log_.log_.str(""); cmd_log_.log_.clear(); }
|
||||
|
||||
using external_log_type = std::function<void(const std::string &)>;
|
||||
|
@ -113,10 +114,10 @@ protected:
|
|||
|
||||
// Print text to the command log for this lua kernel. Used as a replacement impl for lua print.
|
||||
int intf_print(lua_State * L);
|
||||
|
||||
public:
|
||||
// Show the interactive lua console (for debugging purposes)
|
||||
int intf_show_lua_console(lua_State * L);
|
||||
|
||||
protected:
|
||||
// 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);
|
||||
// Execute a protected call, taking a lua_State as argument. For functions pushed into the lua environment, this version should be used, or the function cannot be used by coroutines without segfaulting (since they have a different lua_State pointer). This version is called by the above version.
|
||||
|
|
373
src/scripting/lua_stringx.cpp
Normal file
373
src/scripting/lua_stringx.cpp
Normal file
|
@ -0,0 +1,373 @@
|
|||
/*
|
||||
Copyright (C) 2014 - 2018 by Chris Beck <render787@gmail.com>
|
||||
Part of the Battle for Wesnoth Project https://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_stringx.hpp"
|
||||
#include "scripting/lua_kernel_base.hpp"
|
||||
#include "scripting/lua_common.hpp"
|
||||
#include "scripting/push_check.hpp"
|
||||
|
||||
#include "formula/string_utils.hpp"
|
||||
#include "variable.hpp" // for config_variable_set
|
||||
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
|
||||
#include "lua/lua.h"
|
||||
#include "lua/lauxlib.h"
|
||||
|
||||
namespace lua_stringx {
|
||||
|
||||
/**
|
||||
* Formats a message by interpolating WML variable syntax
|
||||
* Arg 1: (optional) Logger
|
||||
* Arg 2: Message
|
||||
*/
|
||||
static int intf_format(lua_State* L)
|
||||
{
|
||||
config cfg = luaW_checkconfig(L, 2);
|
||||
config_variable_set variables(cfg);
|
||||
if(lua_isstring(L, 1)) {
|
||||
std::string str = lua_tostring(L, 1);
|
||||
lua_push(L, utils::interpolate_variables_into_string(str, variables));
|
||||
return 1;
|
||||
}
|
||||
t_string str = luaW_checktstring(L, 1);
|
||||
lua_push(L, utils::interpolate_variables_into_tstring(str, variables));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a list into human-readable format
|
||||
* Arg 1: default value, used if the list is empty
|
||||
* Arg 2: list of strings
|
||||
*/
|
||||
template<bool conjunct>
|
||||
static int intf_format_list(lua_State* L)
|
||||
{
|
||||
const t_string empty = luaW_checktstring(L, 1);
|
||||
auto values = lua_check<std::vector<t_string>>(L, 2);
|
||||
lua_push(L, (conjunct ? utils::format_conjunct_list : utils::format_disjunct_list)(empty, values));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables indexing a string by an integer, while also treating the stringx module as its metatable.__index
|
||||
*/
|
||||
static int impl_str_index(lua_State* L)
|
||||
{
|
||||
if(lua_type(L, 2) == LUA_TSTRING) {
|
||||
// return stringx[key]
|
||||
lua_getglobal(L, "stringx");
|
||||
lua_pushvalue(L, 2);
|
||||
lua_gettable(L, -2);
|
||||
return 1;
|
||||
} else if(lua_type(L, 2) == LUA_TNUMBER) {
|
||||
// return string.sub(str, key, key)
|
||||
luaW_getglobal(L, "string", "sub");
|
||||
lua_pushvalue(L, 1);
|
||||
lua_pushvalue(L, 2);
|
||||
lua_pushvalue(L, 2);
|
||||
lua_call(L, 3, 1);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a string into parts according to options
|
||||
* Arg 1: String to split
|
||||
* Arg 2: Separator
|
||||
* Arg 3: Options table
|
||||
*/
|
||||
static int intf_str_split(lua_State* L)
|
||||
{
|
||||
enum {BASIC, ESCAPED, PAREN, ANIM} type = BASIC;
|
||||
const std::string& str = luaL_checkstring(L, 1);
|
||||
const std::string& sep = luaL_optstring(L, 2, ",");
|
||||
std::string left, right;
|
||||
int flags = utils::REMOVE_EMPTY | utils::STRIP_SPACES;
|
||||
if(lua_istable(L, 3)) {
|
||||
flags = 0;
|
||||
if(luaW_table_get_def(L, 3, "remove_empty", true)) {
|
||||
flags |= utils::REMOVE_EMPTY;
|
||||
}
|
||||
if(luaW_table_get_def(L, 3, "strip_spaces", true)) {
|
||||
flags |= utils::STRIP_SPACES;
|
||||
}
|
||||
bool anim = luaW_table_get_def(L, 3, "expand_anim", false);
|
||||
if(luaW_tableget(L, 3, "escape")) {
|
||||
if(anim) {
|
||||
return luaL_error(L, "escape and expand_anim options are incompatible!");
|
||||
}
|
||||
type = ESCAPED;
|
||||
left = luaL_checkstring(L, -1);
|
||||
if(left.size() != 1) {
|
||||
return luaL_error(L, "escape must be a single character");
|
||||
}
|
||||
} else if(luaW_tableget(L, 3, "quote")) {
|
||||
left = right = luaL_checkstring(L, -1);
|
||||
if(anim) {
|
||||
type = ANIM;
|
||||
left.push_back('[');
|
||||
right.push_back(']');
|
||||
} else type = PAREN;
|
||||
} else if(luaW_tableget(L, 3, "quote_left") && luaW_tableget(L, 3, "quote_right")) {
|
||||
left = luaL_checkstring(L, -2);
|
||||
right = luaL_checkstring(L, -1);
|
||||
if(anim) {
|
||||
if(left.find_first_of("[]") != std::string::npos || right.find_first_of("[]") != std::string::npos) {
|
||||
return luaL_error(L, "left and right cannot include square brackets [] if expand_anim is enabled");
|
||||
}
|
||||
type = ANIM;
|
||||
left.push_back('[');
|
||||
right.push_back(']');
|
||||
} else type = PAREN;
|
||||
} else if(anim) {
|
||||
type = ANIM;
|
||||
left = "([";
|
||||
right = ")]";
|
||||
}
|
||||
if(type != ESCAPED && left.size() != right.size()) {
|
||||
return luaL_error(L, "left and right need to be strings of the same length");
|
||||
}
|
||||
}
|
||||
switch(type) {
|
||||
case BASIC:
|
||||
lua_push(L, utils::split(str, sep[0], flags));
|
||||
break;
|
||||
case ESCAPED:
|
||||
lua_push(L, utils::quoted_split(str, sep[0], flags, left[0]));
|
||||
break;
|
||||
case PAREN:
|
||||
lua_push(L, utils::parenthetical_split(str, sep[0], left, right, flags));
|
||||
break;
|
||||
case ANIM:
|
||||
lua_push(L, utils::square_parenthetical_split(str, sep[0], left, right, flags));
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a string into parenthesized portions and portions between parenthesized portions
|
||||
* Arg 1: String to split
|
||||
* Arg 2: Possible left parentheses
|
||||
* Arg 3: Matching right parentheses
|
||||
*/
|
||||
static int intf_str_paren_split(lua_State* L)
|
||||
{
|
||||
const std::string& str = luaL_checkstring(L, 1);
|
||||
const std::string& left = luaL_optstring(L, 2, "(");
|
||||
const std::string& right = luaL_optstring(L, 3, ")");
|
||||
if(left.size() != right.size()) {
|
||||
return luaL_error(L, "left and right need to be strings of the same length");
|
||||
}
|
||||
bool strip_spaces = luaL_opt(L, luaW_toboolean, 4, true);
|
||||
lua_push(L, utils::parenthetical_split(str, 0, left, right, strip_spaces ? utils::STRIP_SPACES : 0));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a string into a map
|
||||
* Arg 1: string to split
|
||||
* Arg 2: Separator for items
|
||||
* Arg 3: Separator for key and value
|
||||
*/
|
||||
static int intf_str_map_split(lua_State* L)
|
||||
{
|
||||
const std::string& str = luaL_checkstring(L, 1);
|
||||
const std::string& sep = luaL_optstring(L, 2, ",");
|
||||
const std::string& kv = luaL_optstring(L, 3, ":");
|
||||
std::string dflt;
|
||||
if(sep.size() != 1) {
|
||||
return luaL_error(L, "separator must be a single character");
|
||||
}
|
||||
if(kv.size() != 1) {
|
||||
return luaL_error(L, "key_value_separator must be a single character");
|
||||
}
|
||||
int flags = utils::REMOVE_EMPTY | utils::STRIP_SPACES;
|
||||
if(lua_istable(L, 4)) {
|
||||
flags = 0;
|
||||
if(luaW_table_get_def(L, 4, "remove_empty", true)) {
|
||||
flags |= utils::REMOVE_EMPTY;
|
||||
}
|
||||
if(luaW_table_get_def(L, 4, "strip_spaces", true)) {
|
||||
flags |= utils::STRIP_SPACES;
|
||||
}
|
||||
if(luaW_tableget(L, 4, "default")) {
|
||||
dflt = luaL_checkstring(L, -1);
|
||||
}
|
||||
}
|
||||
lua_push(L, utils::map_split(str, sep[0], kv[0], flags, dflt));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins a list into a string; calls __tostring and __index metamethods
|
||||
* Arg 1: list to join
|
||||
* Arg 2: separator
|
||||
* (arguments can be swapped)
|
||||
*/
|
||||
static int intf_str_join(lua_State* L) {
|
||||
// Support both join(list, [sep]) and join(sep, list)
|
||||
// The latter form means sep:join(list) also works.
|
||||
std::string sep;
|
||||
int list_idx;
|
||||
if(lua_istable(L, 1)) {
|
||||
list_idx = 1;
|
||||
sep = luaL_optstring(L, 2, ",");
|
||||
} else if(lua_istable(L, 2)) {
|
||||
sep = luaL_checkstring(L, 1);
|
||||
list_idx = 2;
|
||||
} else return luaL_error(L, "invalid arguments to join, should have map and separator");
|
||||
std::vector<std::string> pieces;
|
||||
for(int i = 1; i <= luaL_len(L, list_idx); i++) {
|
||||
lua_getglobal(L, "tostring");
|
||||
lua_geti(L, list_idx, i);
|
||||
lua_call(L, 1, 1);
|
||||
pieces.push_back(luaL_checkstring(L, -1));
|
||||
}
|
||||
lua_push(L, utils::join(pieces, sep));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins a map into a string; calls __tostring metamethods (on both key and value) but not __index
|
||||
* Arg 1: list to join
|
||||
* Arg 2: separator for items
|
||||
* Arg 3: separator for key and value
|
||||
* (list argument can be swapped to any position)
|
||||
*/
|
||||
static int intf_str_join_map(lua_State* L) {
|
||||
// Support join_map(map, [sep], [kv_sep]), join_map(sep, map, [kv_sep]), and join_map(sep, kv_sep, map)
|
||||
// The latter forms mean sep:join_map(kv_sep, map) and sep:join_map(map) also work.
|
||||
// If only one separator is given in the first form, it will be sep, not kv_sep
|
||||
std::string sep, kv;
|
||||
int map_idx;
|
||||
if(lua_istable(L, 1)) {
|
||||
map_idx = 1;
|
||||
sep = luaL_optstring(L, 2, ",");
|
||||
kv = luaL_optstring(L, 3, ":");
|
||||
} else if(lua_istable(L, 2)) {
|
||||
sep = luaL_checkstring(L, 1);
|
||||
map_idx = 2;
|
||||
kv = luaL_optstring(L, 3, ":");
|
||||
} else if(lua_istable(L, 3)) {
|
||||
sep = luaL_checkstring(L, 1);
|
||||
kv = luaL_checkstring(L, 2);
|
||||
map_idx = 3;
|
||||
} else return luaL_error(L, "invalid arguments to join_map, should have map, separator, and key_value_separator");
|
||||
std::map<std::string, std::string> pieces;
|
||||
for(lua_pushnil(L); lua_next(L, map_idx); /*pop in loop body*/) {
|
||||
int key_idx = lua_absindex(L, -2), val_idx = lua_absindex(L, -1);
|
||||
lua_getglobal(L, "tostring");
|
||||
lua_pushvalue(L, key_idx);
|
||||
lua_call(L, 1, 1);
|
||||
std::string& val = pieces[luaL_checkstring(L, -1)];
|
||||
lua_getglobal(L, "tostring");
|
||||
lua_pushvalue(L, val_idx);
|
||||
lua_call(L, 1, 1);
|
||||
val = luaL_checkstring(L, -1);
|
||||
lua_settop(L, key_idx);
|
||||
}
|
||||
lua_push(L, utils::join_map(pieces, sep, kv));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims whitespace from the beginning and end of a string
|
||||
*/
|
||||
static int intf_str_trim(lua_State* L)
|
||||
{
|
||||
std::string str = luaL_checkstring(L, 1);
|
||||
boost::trim(str);
|
||||
lua_pushlstring(L, str.c_str(), str.size());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Override string.format to coerce the format to a string
|
||||
static int intf_str_format(lua_State* L)
|
||||
{
|
||||
int nargs = lua_gettop(L);
|
||||
if(luaW_iststring(L, 1)) {
|
||||
// get the tostring() function and call it on the first argument
|
||||
lua_getglobal(L, "tostring");
|
||||
lua_pushvalue(L, 1);
|
||||
lua_call(L, 1, 1);
|
||||
// replace the first argument with the coerced value
|
||||
lua_replace(L, 1);
|
||||
}
|
||||
// grab the original string.format function from the closure...
|
||||
lua_pushvalue(L, lua_upvalueindex(1));
|
||||
// ...move it to the bottom of the stack...
|
||||
lua_insert(L, 1);
|
||||
// ...and finally pass along all the arguments to it.
|
||||
lua_call(L, nargs, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a range string of the form a-b into an interval pair
|
||||
* Accepts the string "infinity" as representing a Very Large Number
|
||||
*/
|
||||
static int intf_parse_range(lua_State* L)
|
||||
{
|
||||
const std::string str = luaL_checkstring(L, 1);
|
||||
auto interval = utils::parse_range(str);
|
||||
lua_pushnumber(L, interval.first);
|
||||
lua_pushnumber(L, interval.second);
|
||||
return 2;
|
||||
}
|
||||
|
||||
int luaW_open(lua_State* L) {
|
||||
auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
|
||||
lk.add_log("Adding stringx module...\n");
|
||||
static luaL_Reg const str_callbacks[] = {
|
||||
{ "split", &intf_str_split },
|
||||
{ "parenthetical_split", &intf_str_paren_split },
|
||||
{ "map_split", &intf_str_map_split },
|
||||
{ "join", &intf_str_join },
|
||||
{ "join_map", &intf_str_join_map },
|
||||
{ "trim", &intf_str_trim },
|
||||
{ "parse_range", &intf_parse_range },
|
||||
{ "vformat", &intf_format },
|
||||
{ "format_conjunct_list", &intf_format_list<true> },
|
||||
{ "format_disjunct_list", &intf_format_list<false> },
|
||||
{ nullptr, nullptr },
|
||||
};
|
||||
lua_newtable(L);
|
||||
luaL_setfuncs(L, str_callbacks, 0);
|
||||
// Set the stringx metatable to index the string module
|
||||
lua_createtable(L, 0, 1);
|
||||
lua_getglobal(L, "string");
|
||||
lua_setfield(L, -2, "__index");
|
||||
lua_setmetatable(L, -2);
|
||||
|
||||
// Set the metatable of strings to index the stringx module instead of the string module
|
||||
lua_pushliteral(L, "");
|
||||
lua_getmetatable(L, -1);
|
||||
lua_pushcfunction(L, &impl_str_index);
|
||||
lua_setfield(L, -2, "__index");
|
||||
lua_setmetatable(L, -2);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Override string.format so it can accept a t_string
|
||||
lua_getglobal(L, "string");
|
||||
lua_getfield(L, -1, "format");
|
||||
lua_pushcclosure(L, &intf_str_format, 1);
|
||||
lua_setfield(L, -2, "format");
|
||||
lua_pop(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
21
src/scripting/lua_stringx.hpp
Normal file
21
src/scripting/lua_stringx.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
Copyright (C) 2014 - 2018 by Chris Beck <render787@gmail.com>
|
||||
Part of the Battle for Wesnoth Project https://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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
struct lua_State;
|
||||
|
||||
namespace lua_stringx {
|
||||
int luaW_open(lua_State* L);
|
||||
}
|
266
src/scripting/lua_wml.cpp
Normal file
266
src/scripting/lua_wml.cpp
Normal file
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
Copyright (C) 2014 - 2018 by Chris Beck <render787@gmail.com>
|
||||
Part of the Battle for Wesnoth Project https://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_wml.hpp"
|
||||
#include "scripting/lua_kernel_base.hpp"
|
||||
#include "scripting/lua_common.hpp"
|
||||
|
||||
#include "serialization/string_utils.hpp"
|
||||
#include "serialization/schema_validator.hpp"
|
||||
#include "serialization/parser.hpp"
|
||||
#include "serialization/preprocessor.hpp"
|
||||
#include "variable.hpp" // for config_variable_set
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "lua/lua.h"
|
||||
#include "lua/lauxlib.h"
|
||||
|
||||
namespace lua_wml {
|
||||
|
||||
/**
|
||||
* Dumps a wml table or userdata wml object into a pretty string.
|
||||
* - Arg 1: wml table or vconfig userdata
|
||||
* - Ret 1: string
|
||||
*/
|
||||
static int intf_wml_tostring(lua_State* L) {
|
||||
const config& arg = luaW_checkconfig(L, 1);
|
||||
std::ostringstream stream;
|
||||
write(stream, arg);
|
||||
lua_pushstring(L, stream.str().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a WML file into a config
|
||||
* - Arg 1: WML file path
|
||||
* - Arg 2: (optional) Array of preprocessor defines, or false to skip preprocessing (true is also valid)
|
||||
* - Arg 3: (optional) Path to a schema file for validation (omit for no validation)
|
||||
* - Ret: config
|
||||
*/
|
||||
static int intf_load_wml(lua_State* L)
|
||||
{
|
||||
std::string file = luaL_checkstring(L, 1);
|
||||
bool preprocess = true;
|
||||
preproc_map defines_map;
|
||||
if(lua_type(L, 2) == LUA_TBOOLEAN) {
|
||||
preprocess = luaW_toboolean(L, 2);
|
||||
} else if(lua_type(L, 2) == LUA_TTABLE || lua_type(L, 2) == LUA_TUSERDATA) {
|
||||
lua_len(L, 2);
|
||||
int n = lua_tonumber(L, -1);
|
||||
lua_pop(L, 1);
|
||||
for(int i = 0; i < n; i++) {
|
||||
lua_geti(L, 2, i);
|
||||
if(!lua_isstring(L, -1)) {
|
||||
return luaL_argerror(L, 2, "expected bool or array of strings");
|
||||
}
|
||||
std::string define = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
if(!define.empty()) {
|
||||
defines_map.emplace(define, preproc_define(define));
|
||||
}
|
||||
}
|
||||
} else if(!lua_isnoneornil(L, 2)) {
|
||||
return luaL_argerror(L, 2, "expected bool or array of strings");
|
||||
}
|
||||
std::string schema_path = luaL_optstring(L, 3, "");
|
||||
std::shared_ptr<schema_validation::schema_validator> validator;
|
||||
if(!schema_path.empty()) {
|
||||
validator.reset(new schema_validation::schema_validator(filesystem::get_wml_location(schema_path)));
|
||||
validator->set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
|
||||
}
|
||||
std::string wml_file = filesystem::get_wml_location(file);
|
||||
filesystem::scoped_istream stream;
|
||||
config result;
|
||||
if(preprocess) {
|
||||
stream = preprocess_file(wml_file, &defines_map);
|
||||
} else {
|
||||
stream.reset(new std::ifstream(wml_file));
|
||||
}
|
||||
read(result, *stream, validator.get());
|
||||
luaW_pushconfig(L, result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a WML string into a config; does not preprocess or validate
|
||||
* - Arg 1: WML string
|
||||
* - Ret: config
|
||||
*/
|
||||
static int intf_parse_wml(lua_State* L)
|
||||
{
|
||||
std::string wml = luaL_checkstring(L, 1);
|
||||
std::string schema_path = luaL_optstring(L, 2, "");
|
||||
std::shared_ptr<schema_validation::schema_validator> validator;
|
||||
if(!schema_path.empty()) {
|
||||
validator.reset(new schema_validation::schema_validator(filesystem::get_wml_location(schema_path)));
|
||||
validator->set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
|
||||
}
|
||||
config result;
|
||||
read(result, wml, validator.get());
|
||||
luaW_pushconfig(L, result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a clone (deep copy) of the passed config, which can be either a normal config or a vconfig
|
||||
* If it is a vconfig, the underlying config is also cloned.
|
||||
* - Arg 1: a config
|
||||
* - Ret: the cloned config
|
||||
*/
|
||||
static int intf_clone_wml(lua_State* L)
|
||||
{
|
||||
const vconfig* vcfg = nullptr;
|
||||
const config& cfg = luaW_checkconfig(L, 1, vcfg);
|
||||
if(vcfg) {
|
||||
config clone_underlying = vcfg->get_config();
|
||||
vconfig clone(clone_underlying);
|
||||
luaW_pushvconfig(L, clone);
|
||||
} else {
|
||||
luaW_pushconfig(L, cfg);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates variables into a WML table, including [insert_tag]
|
||||
* Arg 1: WML table to interpolate into
|
||||
* Arg 2: WML table of variables
|
||||
*/
|
||||
static int intf_wml_interpolate(lua_State* L)
|
||||
{
|
||||
config cfg = luaW_checkconfig(L, 1), vars_cfg = luaW_checkconfig(L, 2);
|
||||
config_variable_set vars(vars_cfg);
|
||||
vconfig vcfg(cfg, vars);
|
||||
luaW_pushconfig(L, vcfg.get_parsed_config());
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a WML table matches a filter
|
||||
* Arg 1: table to test
|
||||
* Arg 2: filter
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two WML tables
|
||||
* Arg 1: base table
|
||||
* Arg 2: table to merge in
|
||||
*/
|
||||
static int intf_wml_merge(lua_State* L)
|
||||
{
|
||||
config base = luaW_checkconfig(L, 1);
|
||||
config merge = luaW_checkconfig(L, 2);
|
||||
const std::string mode = lua_isstring(L, 3) ? luaL_checkstring(L, 3) : "merge";
|
||||
if(mode == "append") {
|
||||
base.merge_attributes(merge);
|
||||
base.append_children(merge);
|
||||
} else {
|
||||
if(mode == "replace") {
|
||||
for(const auto& c : merge.all_children_range()) {
|
||||
base.clear_children(c.key);
|
||||
}
|
||||
} else if(mode != "merge") {
|
||||
return luaL_argerror(L, 3, "invalid merge mode - must be merge, append, or replace");
|
||||
}
|
||||
base.merge_with(merge);
|
||||
}
|
||||
luaW_pushconfig(L, base);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a diff of two WML tables
|
||||
* Arg 1: left table
|
||||
* Arg 2: right table
|
||||
*/
|
||||
static int intf_wml_diff(lua_State* L)
|
||||
{
|
||||
config lhs = luaW_checkconfig(L, 1);
|
||||
config rhs = luaW_checkconfig(L, 2);
|
||||
luaW_pushconfig(L, lhs.get_diff(rhs));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a diff to a WML table
|
||||
* Arg 1: base table
|
||||
* Arg 2: WML diff
|
||||
*/
|
||||
static int intf_wml_patch(lua_State* L)
|
||||
{
|
||||
config base = luaW_checkconfig(L, 1);
|
||||
config patch = luaW_checkconfig(L, 2);
|
||||
base.apply_diff(patch);
|
||||
luaW_pushconfig(L, base);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if two WML tables are equal (have the same keys and values, same tags, recursively)
|
||||
* Arg 1: left table
|
||||
* Arg 2: right table
|
||||
*/
|
||||
static int intf_wml_equal(lua_State* L)
|
||||
{
|
||||
config left = luaW_checkconfig(L, 1);
|
||||
config right = luaW_checkconfig(L, 2);
|
||||
lua_pushboolean(L, left == right);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a table represents a valid WML table
|
||||
* Arg 1: table
|
||||
*/
|
||||
static int intf_wml_valid(lua_State* L)
|
||||
{
|
||||
config test;
|
||||
if(luaW_toconfig(L, 1, test)) {
|
||||
// The validate_wml call is PROBABLY redundant, but included just in case validation changes and toconfig isn't updated to match
|
||||
lua_pushboolean(L, test.validate_wml());
|
||||
} else lua_pushboolean(L, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int luaW_open(lua_State* L) {
|
||||
auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
|
||||
lk.add_log("Adding wml module...\n");
|
||||
static luaL_Reg const wml_callbacks[]= {
|
||||
{ "load", &intf_load_wml},
|
||||
{ "parse", &intf_parse_wml},
|
||||
{ "clone", &intf_clone_wml},
|
||||
{ "merge", &intf_wml_merge},
|
||||
{ "diff", &intf_wml_diff},
|
||||
{ "patch", &intf_wml_patch},
|
||||
{ "equal", &intf_wml_equal},
|
||||
{ "valid", &intf_wml_valid},
|
||||
{ "matches_filter", &intf_wml_matches_filter},
|
||||
{ "tostring", &intf_wml_tostring},
|
||||
{ "interpolate", &intf_wml_interpolate},
|
||||
{ nullptr, nullptr },
|
||||
};
|
||||
lua_newtable(L);
|
||||
luaL_setfuncs(L, wml_callbacks, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
21
src/scripting/lua_wml.hpp
Normal file
21
src/scripting/lua_wml.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
Copyright (C) 2014 - 2018 by Chris Beck <render787@gmail.com>
|
||||
Part of the Battle for Wesnoth Project https://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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
struct lua_State;
|
||||
|
||||
namespace lua_wml {
|
||||
int luaW_open(lua_State* L);
|
||||
}
|
Loading…
Add table
Reference in a new issue