Add wesnoth.eval_formula which compiles and evaluates a formula in the formula engine

This commit is contained in:
Celtic Minstrel 2016-02-25 21:30:55 -05:00
parent ca382018b7
commit 4d1c9bdd43
4 changed files with 275 additions and 0 deletions

View file

@ -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 = "<group>"; };
91F462931C7117400050A9C9 /* drop_down_list.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = drop_down_list.hpp; sourceTree = "<group>"; };
91FAC70B1C80168600DAB2C3 /* group.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = group.hpp; sourceTree = "<group>"; };
91FAC7081C7F931900DAB2C3 /* lua_formula_bridge.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lua_formula_bridge.hpp; sourceTree = "<group>"; };
91FAC7091C7FBC2C00DAB2C3 /* lua_formula_bridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = lua_formula_bridge.cpp; sourceTree = "<group>"; };
B504B94A1284C06B00261FE9 /* tips.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tips.cpp; sourceTree = "<group>"; };
B504B94B1284C06B00261FE9 /* tips.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = tips.hpp; sourceTree = "<group>"; };
B508D13E10013BF900B12852 /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Growl.framework; path = lib/Growl.framework; sourceTree = "<group>"; };
@ -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;
};

View file

@ -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 },

View file

@ -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<variant> 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<variant,variant> 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<formula_input>* 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<variant> {
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<variant> 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<variant> 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<std::string, std::vector<variant> > 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<variant,variant> result;
for(auto& p : build) {
result[variant(p.first)] = variant(&p.second);
}
return variant(&result);
} else if(key == "__attributes") {
std::map<variant,variant> 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<formula_input>* 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<variant,variant>::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<unit_callable>()) {
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<formula_input> 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<formula_callable> 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;
}

View file

@ -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