diff --git a/projectfiles/Xcode/Wesnoth.xcodeproj/project.pbxproj b/projectfiles/Xcode/Wesnoth.xcodeproj/project.pbxproj index f16e8fa386a..3cb749247b6 100644 --- a/projectfiles/Xcode/Wesnoth.xcodeproj/project.pbxproj +++ b/projectfiles/Xcode/Wesnoth.xcodeproj/project.pbxproj @@ -717,6 +717,7 @@ 91F462841C71139C0050A9C9 /* preferences_dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91F462821C71139B0050A9C9 /* preferences_dialog.cpp */; }; 91F462881C7115C50050A9C9 /* combobox.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91F462861C7115C50050A9C9 /* combobox.cpp */; }; 91F462941C7117400050A9C9 /* drop_down_list.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91F462921C7117400050A9C9 /* drop_down_list.cpp */; }; + 91FAC70A1C7FBC3400DAB2C3 /* lua_formula_bridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91FAC7091C7FBC2C00DAB2C3 /* lua_formula_bridge.cpp */; }; B504B94C1284C06B00261FE9 /* tips.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B504B94A1284C06B00261FE9 /* tips.cpp */; }; B508D13F10013BF900B12852 /* Growl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B508D13E10013BF900B12852 /* Growl.framework */; }; B508D14B10013E4700B12852 /* Growl Registration Ticket.growlRegDict in Resources */ = {isa = PBXBuildFile; fileRef = B508D14A10013E4700B12852 /* Growl Registration Ticket.growlRegDict */; }; @@ -1713,6 +1714,8 @@ 91F462921C7117400050A9C9 /* drop_down_list.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = drop_down_list.cpp; sourceTree = ""; }; 91F462931C7117400050A9C9 /* drop_down_list.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = drop_down_list.hpp; sourceTree = ""; }; 91FAC70B1C80168600DAB2C3 /* group.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = group.hpp; sourceTree = ""; }; + 91FAC7081C7F931900DAB2C3 /* lua_formula_bridge.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lua_formula_bridge.hpp; sourceTree = ""; }; + 91FAC7091C7FBC2C00DAB2C3 /* lua_formula_bridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = lua_formula_bridge.cpp; sourceTree = ""; }; B504B94A1284C06B00261FE9 /* tips.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tips.cpp; sourceTree = ""; }; B504B94B1284C06B00261FE9 /* tips.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = tips.hpp; sourceTree = ""; }; B508D13E10013BF900B12852 /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Growl.framework; path = lib/Growl.framework; sourceTree = ""; }; @@ -4100,6 +4103,8 @@ 91B621E61B76BB0B00B00E0F /* lua_cpp_function.hpp */, ECA4A6781A1EC319006BCCF2 /* lua_fileops.cpp */, 91B621E71B76BB0E00B00E0F /* lua_fileops.hpp */, + 91FAC7091C7FBC2C00DAB2C3 /* lua_formula_bridge.cpp */, + 91FAC7081C7F931900DAB2C3 /* lua_formula_bridge.hpp */, ECA1E0F11A1271AC00426E00 /* lua_gui2.cpp */, 91B621E81B76BB1100B00E0F /* lua_gui2.hpp */, EC218E9F1A106648007C910C /* lua_kernel_base.cpp */, @@ -5182,6 +5187,7 @@ 91DCA6891C9066CC0030F8D0 /* unit_preview_pane.cpp in Sources */, 91DCA68D1C9066EC0030F8D0 /* unit_recruit.cpp in Sources */, 9122417C1CAAB7B7008B347F /* loadscreen.cpp in Sources */, + 91FAC70A1C7FBC3400DAB2C3 /* lua_formula_bridge.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/scripting/game_lua_kernel.cpp b/src/scripting/game_lua_kernel.cpp index 327a49b49d0..a07470b7abc 100644 --- a/src/scripting/game_lua_kernel.cpp +++ b/src/scripting/game_lua_kernel.cpp @@ -75,6 +75,7 @@ #include "scripting/lua_api.hpp" // for luaW_toboolean, etc #include "scripting/lua_common.hpp" #include "scripting/lua_cpp_function.hpp" +#include "scripting/lua_formula_bridge.hpp" #include "scripting/lua_gui2.hpp" // for show_gamestate_inspector #include "scripting/lua_pathfind_cost_calculator.hpp" #include "scripting/lua_race.hpp" @@ -4261,6 +4262,7 @@ game_lua_kernel::game_lua_kernel(CVideo * video, game_state & gs, play_controlle { "debug", &intf_debug }, { "debug_ai", &intf_debug_ai }, { "eval_conditional", &intf_eval_conditional }, + { "eval_formula", &lua_formula_bridge::intf_eval_formula}, { "get_era", &intf_get_era }, { "get_image_size", &intf_get_image_size }, { "get_time_stamp", &intf_get_time_stamp }, diff --git a/src/scripting/lua_formula_bridge.cpp b/src/scripting/lua_formula_bridge.cpp new file mode 100644 index 00000000000..8fc9ffc6776 --- /dev/null +++ b/src/scripting/lua_formula_bridge.cpp @@ -0,0 +1,242 @@ +/* + 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_formula_bridge.hpp" + +#include "boost/variant/static_visitor.hpp" + +#include "scripting/game_lua_kernel.hpp" +#include "scripting/lua_api.hpp" +#include "scripting/lua_common.hpp" +#include "lua/lauxlib.h" +#include "lua/lua.h" +#include "formula/callable_objects.hpp" +#include "formula/formula.hpp" +#include "formula/function.hpp" +#include "variable.hpp" + +void luaW_pushfaivariant(lua_State* L, variant val); +variant luaW_tofaivariant(lua_State* L, int i); + +static const char*const formula_id_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"; + +using namespace game_logic; + +class lua_callable : public formula_callable { + lua_State* mState; + int table_i; +public: + lua_callable(lua_State* L, int i) : mState(L), table_i(lua_absindex(L,i)) {} + variant get_value(const std::string& key) const { + if(key == "__list") { + std::vector values; + size_t n = lua_rawlen(mState, table_i); + if(n == 0) { + return variant(); + } + for(size_t i = 1; i <= n; i++) { + lua_pushinteger(mState, i); + lua_gettable(mState, table_i); + values.push_back(luaW_tofaivariant(mState, -1)); + } + return variant(&values); + } else if(key == "__map") { + std::map values; + for(lua_pushnil(mState); lua_next(mState, table_i); lua_pop(mState, 1)) { + values[luaW_tofaivariant(mState, -2)] = luaW_tofaivariant(mState, -1); + } + return variant(&values); + } + lua_pushlstring(mState, key.c_str(), key.size()); + lua_gettable(mState, table_i); + variant result = luaW_tofaivariant(mState, -1); + lua_pop(mState, 1); + return result; + } + void get_inputs(std::vector* inputs) const { + inputs->push_back(formula_input("__list", FORMULA_READ_ONLY)); + inputs->push_back(formula_input("__map", FORMULA_READ_ONLY)); + for(lua_pushnil(mState); lua_next(mState, table_i); lua_pop(mState,1)) { + if(lua_isstring(mState, -2) && !lua_isnumber(mState, -2)) { + std::string key = lua_tostring(mState, -2); + if(key.find_first_not_of(formula_id_chars) != std::string::npos) { + inputs->push_back(formula_input(key, FORMULA_READ_ONLY)); + } + } + } + } +}; + +class fai_variant_visitor : public boost::static_visitor { +public: + variant operator()(bool b) const {return variant(b ? 1 : 0);} + variant operator()(int i) const {return variant(i);} + variant operator()(unsigned long long i) const {return variant(i);} + variant operator()(double i) const {return variant(i * 1000, variant::DECIMAL_VARIANT);} + variant operator()(const std::string& s) const {return variant(s);} // TODO: Should comma-separated lists of stuff be returned as a list? The challenge is to distinguish them from ordinary strings that happen to contain a comma (or should we assume that such strings will be translatable?) + variant operator()(const t_string& s) const {return variant(s.str());} + variant operator()(boost::blank) const {return variant();} +}; + +class config_callable : public formula_callable { + const config& cfg; +public: + config_callable(const config& c) : cfg(c) {} + variant get_value(const std::string& key) const { + if(cfg.has_attribute(key)) { + return cfg[key].apply_visitor(fai_variant_visitor()); + } else if(cfg.has_child(key)) { + std::vector result; + for(const config& child : cfg.child_range(key)) { + result.push_back(variant(new config_callable(child))); + } + return variant(&result); + } else if(key == "__all_children") { + std::vector result; + for(const config::any_child& child : cfg.all_children_range()) { + const variant cfg_child(new config_callable(child.cfg)); + const variant kv(new key_value_pair(variant(child.key), cfg_child)); + result.push_back(kv); + } + return variant(&result); + } else if(key == "__children") { + std::map > build; + for(const config::any_child& child : cfg.all_children_range()) { + const variant cfg_child(new config_callable(child.cfg)); + build[child.key].push_back(cfg_child); + } + std::map result; + for(auto& p : build) { + result[variant(p.first)] = variant(&p.second); + } + return variant(&result); + } else if(key == "__attributes") { + std::map result; + for(const config::attribute& val : cfg.attribute_range()) { + result[variant(val.first)] = val.second.apply_visitor(fai_variant_visitor()); + } + return variant(&result); + } else return variant(); + } + void get_inputs(std::vector* inputs) const { + inputs->push_back(formula_input("__all_children", FORMULA_READ_ONLY)); + inputs->push_back(formula_input("__children", FORMULA_READ_ONLY)); + inputs->push_back(formula_input("__attributes", FORMULA_READ_ONLY)); + for(const config::attribute& val : cfg.attribute_range()) { + if(val.first.find_first_not_of(formula_id_chars) != std::string::npos) { + inputs->push_back(formula_input(val.first, FORMULA_READ_ONLY)); + } + } + } +}; + +void luaW_pushfaivariant(lua_State* L, variant val) { + if(val.is_int()) { + lua_pushinteger(L, val.as_int()); + } else if(val.is_decimal()) { + lua_pushnumber(L, val.as_decimal() / 1000.0); + } else if(val.is_string()) { + const std::string result_string = val.as_string(); + lua_pushlstring(L, result_string.c_str(), result_string.size()); + } else if(val.is_list()) { + lua_newtable(L); + for(const variant& v : val.as_list()) { + lua_pushinteger(L, lua_rawlen(L, -1) + 1); + luaW_pushfaivariant(L, v); + lua_settable(L, -3); + } + } else if(val.is_map()) { + typedef std::map::value_type kv_type; + lua_newtable(L); + for(const kv_type& v : val.as_map()) { + luaW_pushfaivariant(L, v.first); + luaW_pushfaivariant(L, v.second); + lua_settable(L, -3); + } + } else if(val.is_callable()) { + // First try a few special cases (well, currently, only one) + if(unit_callable* u = val.try_convert()) { + std::string id = u->query_value("id").as_string(); + luaW_getglobal(L, "wesnoth", "get_unit", NULL); + lua_pushstring(L, id.c_str()); + luaW_pcall(L, 1, 1); + } else { + // If those fail, convert generically to a map + const formula_callable* obj = val.as_callable(); + std::vector inputs; + obj->get_inputs(&inputs); + lua_newtable(L); + for(const formula_input& attr : inputs) { + if(attr.access == FORMULA_WRITE_ONLY) { + continue; + } + lua_pushstring(L, attr.name.c_str()); + luaW_pushfaivariant(L, obj->query_value(attr.name)); + lua_settable(L, -3); + } + } + } else if(val.is_null()) { + lua_pushnil(L); + } +} + +variant luaW_tofaivariant(lua_State* L, int i) { + switch(lua_type(L, i)) { + case LUA_TBOOLEAN: + return variant(lua_tointeger(L, i)); + case LUA_TNUMBER: + return variant(lua_tonumber(L, i), variant::DECIMAL_VARIANT); + case LUA_TSTRING: + return variant(lua_tostring(L, i)); + case LUA_TTABLE: + return variant(new lua_callable(L, i)); + case LUA_TUSERDATA: + static t_string tstr; + static vconfig vcfg = vconfig::unconstructed_vconfig(); + if(luaW_totstring(L, i, tstr)) { + return variant(tstr.str()); + } else if(luaW_tovconfig(L, i, vcfg)) { + return variant(new config_callable(vcfg.get_parsed_config())); + } else if(unit* u = luaW_tounit(L, i)) { + return variant(new unit_callable(*u)); + } + break; + } + return variant(); +} + +/** + * Evaluates a formula in the formula engine. + * - Arg 1: Formula string. + * - Arg 2: optional context; can be a unit or a Lua table. + * - Ret 1: Result of the formula. + */ +int lua_formula_bridge::intf_eval_formula(lua_State *L) +{ + using namespace game_logic; + if(!lua_isstring(L, 1)) { + luaL_typerror(L, 1, "string"); + } + const formula form(lua_tostring(L, 1)); + boost::shared_ptr context, fallback; + if(unit* u = luaW_tounit(L, 2)) { + context.reset(new unit_callable(*u)); + } else if(lua_istable(L, 2)) { + context.reset(new lua_callable(L, 2)); + } else { + context.reset(new map_formula_callable); + } + variant result = form.evaluate(*context); + luaW_pushfaivariant(L, result); + return 1; +} diff --git a/src/scripting/lua_formula_bridge.hpp b/src/scripting/lua_formula_bridge.hpp new file mode 100644 index 00000000000..8cc8fd4ba9b --- /dev/null +++ b/src/scripting/lua_formula_bridge.hpp @@ -0,0 +1,25 @@ +/* + 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 LUA_FORMULA_BRIDGE_HPP_INCLUDED +#define LUA_FORMULA_BRIDGE_HPP_INCLUDED + +struct lua_State; + +namespace lua_formula_bridge { + + int intf_eval_formula(lua_State*); + +} // end namespace lua_formula_bridge + +#endif