The Battle for Wesnoth  1.17.12+dev
lua_formula_bridge.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2017 - 2022
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
16 
17 #include "game_board.hpp"
19 #include "scripting/lua_unit.hpp"
20 #include "scripting/lua_common.hpp"
21 #include "scripting/lua_team.hpp"
24 #include "lua/lauxlib.h"
25 #include "formula/callable_objects.hpp"
26 #include "formula/formula.hpp"
27 #include "variable.hpp"
28 
29 #include "resources.hpp"
30 #include "units/map.hpp"
31 #include "units/unit.hpp"
32 
33 static const char formulaKey[] = "formula";
34 
35 using namespace wfl;
36 
37 void luaW_pushfaivariant(lua_State* L, variant val);
38 variant luaW_tofaivariant(lua_State* L, int i);
39 
41  lua_State* mState;
42  int table_i;
43 public:
44  lua_callable(lua_State* L, int i) : mState(L), table_i(lua_absindex(L,i)) {}
45  variant get_value(const std::string& key) const {
46  if(key == "__list") {
47  std::vector<variant> values;
48  std::size_t n = lua_rawlen(mState, table_i);
49  if(n == 0) {
50  return variant();
51  }
52  for(std::size_t i = 1; i <= n; i++) {
53  lua_pushinteger(mState, i);
54  lua_gettable(mState, table_i);
55  values.push_back(luaW_tofaivariant(mState, -1));
56  }
57  return variant(values);
58  } else if(key == "__map") {
59  std::map<variant,variant> values;
60  for(lua_pushnil(mState); lua_next(mState, table_i); lua_pop(mState, 1)) {
61  values[luaW_tofaivariant(mState, -2)] = luaW_tofaivariant(mState, -1);
62  }
63  return variant(values);
64  }
65  lua_pushlstring(mState, key.c_str(), key.size());
66  lua_gettable(mState, table_i);
67  variant result = luaW_tofaivariant(mState, -1);
68  lua_pop(mState, 1);
69  return result;
70  }
71  void get_inputs(formula_input_vector& inputs) const {
72  add_input(inputs, "__list");
73  add_input(inputs, "__map");
74  for(lua_pushnil(mState); lua_next(mState, table_i); lua_pop(mState,1)) {
75  lua_pushvalue(mState, -2);
76  bool is_valid_key = (lua_type(mState, -1) == LUA_TSTRING) && !lua_isnumber(mState, -1);
77  lua_pop(mState, 1);
78  if(is_valid_key) {
79  std::string key = lua_tostring(mState, -2);
80  if(key.find_first_not_of(formula::id_chars) != std::string::npos) {
81  add_input(inputs, key);
82  }
83  }
84  }
85  }
86  int do_compare(const formula_callable* other) const {
87  const lua_callable* lua = dynamic_cast<const lua_callable*>(other);
88  if(lua == nullptr) {
89  return formula_callable::do_compare(other);
90  }
91  if(mState == lua->mState) { // Which should always be the case, but let's be safe here
92  if(lua_compare(mState, table_i, lua->table_i, LUA_OPEQ)) {
93  return 0;
94  }
95  int top = lua_gettop(mState);
96  if(lua_getmetatable(mState, table_i)) {
97  lua_getfield(mState, -1, "__lt");
98  if(!lua_isnoneornil(mState, -1)) {
99  if(lua_getmetatable(mState, lua->table_i)) {
100  lua_getfield(mState, -1, "__lt");
101  if(!lua_isnoneornil(mState, -1)) {
102  lua_settop(mState, top);
103  return lua_compare(mState, table_i, lua->table_i, LUA_OPLT) ? -1 : 1;
104  }
105  if(lua_compare(mState, -4, -2, LUA_OPEQ)) {
106  lua_settop(mState, top);
107  return 0;
108  }
109  const void* lhs = lua_topointer(mState, -4);
110  const void* rhs = lua_topointer(mState, -2);
111  lua_settop(mState, top);
112  return lhs < rhs ? -1 : (lhs > rhs ? 1 : 0);
113  }
114  }
115  }
116  lua_settop(mState, top);
117  return lua_topointer(mState, -2) < lua_topointer(mState, -1) ? -1 : 1;
118  }
119  return mState < lua->mState ? -1 : 1;
120  }
121 };
122 
123 void luaW_pushfaivariant(lua_State* L, variant val) {
124  if(val.is_int()) {
125  lua_pushinteger(L, val.as_int());
126  } else if(val.is_decimal()) {
127  lua_pushnumber(L, val.as_decimal() / 1000.0);
128  } else if(val.is_string()) {
129  const std::string result_string = val.as_string();
130  lua_pushlstring(L, result_string.c_str(), result_string.size());
131  } else if(val.is_list()) {
132  lua_newtable(L);
133  for(const variant& v : val.as_list()) {
134  lua_pushinteger(L, lua_rawlen(L, -1) + 1);
135  luaW_pushfaivariant(L, v);
136  lua_settable(L, -3);
137  }
138  } else if(val.is_map()) {
139  typedef std::map<variant,variant>::value_type kv_type;
140  lua_newtable(L);
141  for(const kv_type& v : val.as_map()) {
142  luaW_pushfaivariant(L, v.first);
143  luaW_pushfaivariant(L, v.second);
144  lua_settable(L, -3);
145  }
146  } else if(val.is_callable()) {
147  // First try a few special cases
148  if(auto u_ref = val.try_convert<unit_callable>()) {
149  const unit& u = u_ref->get_unit();
151  if(&*un_it == &u) {
153  } else {
154  luaW_pushunit(L, u.side(), u.underlying_id());
155  }
156  } else if(auto ut_ref = val.try_convert<unit_type_callable>()) {
157  const unit_type& ut = ut_ref->get_unit_type();
158  luaW_pushunittype(L, ut);
159  } else if(auto atk_ref = val.try_convert<attack_type_callable>()) {
160  const auto& atk = atk_ref->get_attack_type();
161  luaW_pushweapon(L, atk.shared_from_this());
162  } else if(auto team_ref = val.try_convert<team_callable>()) {
163  auto t = team_ref->get_team();
164  luaW_pushteam(L, t);
165  } else if(auto loc_ref = val.try_convert<location_callable>()) {
166  luaW_pushlocation(L, loc_ref->loc());
167  } else {
168  // If those fail, convert generically to a map
169  auto obj = val.as_callable();
170  formula_input_vector inputs;
171  obj->get_inputs(inputs);
172  lua_newtable(L);
173  for(const formula_input& attr : inputs) {
174  if(attr.access == formula_access::write_only) {
175  continue;
176  }
177  lua_pushstring(L, attr.name.c_str());
178  luaW_pushfaivariant(L, obj->query_value(attr.name));
179  lua_settable(L, -3);
180  }
181  }
182  } else if(val.is_null()) {
183  lua_pushnil(L);
184  }
185 }
186 
187 variant luaW_tofaivariant(lua_State* L, int i) {
188  switch(lua_type(L, i)) {
189  case LUA_TBOOLEAN:
190  return variant(lua_tointeger(L, i));
191  case LUA_TNUMBER:
192  return variant(lua_tonumber(L, i), variant::DECIMAL_VARIANT);
193  case LUA_TSTRING:
194  return variant(lua_tostring(L, i));
195  case LUA_TTABLE:
196  return variant(std::make_shared<lua_callable>(L, i));
197  case LUA_TUSERDATA:
198  static t_string tstr;
199  static vconfig vcfg = vconfig::unconstructed_vconfig();
200  static map_location loc;
201  if(luaW_totstring(L, i, tstr)) {
202  return variant(tstr.str());
203  } else if(luaW_tovconfig(L, i, vcfg)) {
204  return variant(std::make_shared<config_callable>(vcfg.get_parsed_config()));
205  } else if(unit* u = luaW_tounit(L, i)) {
206  return variant(std::make_shared<unit_callable>(*u));
207  } else if(const unit_type* ut = luaW_tounittype(L, i)) {
208  return variant(std::make_shared<unit_type_callable>(*ut));
209  } else if(const_attack_ptr atk = luaW_toweapon(L, i)) {
210  return variant(std::make_shared<attack_type_callable>(*atk));
211  } else if(team* t = luaW_toteam(L, i)) {
212  return variant(std::make_shared<team_callable>(*t));
213  } else if(luaW_tolocation(L, i, loc)) {
214  return variant(std::make_shared<location_callable>(loc));
215  }
216  break;
217  }
218  return variant();
219 }
220 
221 /**
222  * Evaluates a formula in the formula engine.
223  * - Arg 1: Formula string.
224  * - Arg 2: optional context; can be a unit or a Lua table.
225  * - Ret 1: Result of the formula.
226  */
228 {
229  bool need_delete = false;
230  fwrapper* form;
231  if(void* ud = luaL_testudata(L, 1, formulaKey)) {
232  form = static_cast<fwrapper*>(ud);
233  } else {
234  need_delete = true;
235  form = new fwrapper(luaL_checkstring(L, 1));
236  }
237  std::shared_ptr<formula_callable> context, fallback;
238  if(unit* u = luaW_tounit(L, 2)) {
239  context.reset(new unit_callable(*u));
240  } else if(const unit_type* ut = luaW_tounittype(L, 2)) {
241  context.reset(new unit_type_callable(*ut));
242  } else if(const_attack_ptr atk = luaW_toweapon(L, 2)) {
243  context.reset(new attack_type_callable(*atk));
244  } else if(team* t = luaW_toteam(L, 2)) {
245  context.reset(new team_callable(*t));
246  } else if(lua_istable(L, 2)) {
247  context.reset(new lua_callable(L, 2));
248  } else {
249  context.reset(new map_formula_callable);
250  }
251  variant result = form->evaluate(*context);
252  luaW_pushfaivariant(L, result);
253  if(need_delete) {
254  delete form;
255  }
256  return 1;
257 }
258 
260 {
261  if(!lua_isstring(L, 1)) {
262  luaW_type_error(L, 1, "string");
263  }
264  new(L) fwrapper(lua_tostring(L, 1));
265  luaL_setmetatable(L, formulaKey);
266  return 1;
267 }
268 
270  : formula_ptr(new formula(code, functions))
271 {
272 }
273 
275 {
276  if(formula_ptr) {
277  return formula_ptr->str();
278  }
279  return "";
280 }
281 
283 {
284  if(formula_ptr) {
285  return formula_ptr->evaluate(variables, fdb);
286  }
287  return variant();
288 }
289 
290 static int impl_formula_collect(lua_State* L)
291 {
292  lua_formula_bridge::fwrapper* form = static_cast<lua_formula_bridge::fwrapper*>(lua_touserdata(L, 1));
293  form->~fwrapper();
294  return 0;
295 }
296 
297 static int impl_formula_tostring(lua_State* L)
298 {
299  lua_formula_bridge::fwrapper* form = static_cast<lua_formula_bridge::fwrapper*>(lua_touserdata(L, 1));
300  const std::string str = form->str();
301  lua_pushlstring(L, str.c_str(), str.size());
302  return 1;
303 }
304 
306 {
307  luaL_newmetatable(L, formulaKey);
308  lua_pushcfunction(L, impl_formula_collect);
309  lua_setfield(L, -2, "__gc");
310  lua_pushcfunction(L, impl_formula_tostring);
311  lua_setfield(L, -2, "__tostring");
312  lua_pushcfunction(L, intf_eval_formula);
313  lua_setfield(L, -2, "__call");
314  lua_pushstring(L, "formula");
315  lua_setfield(L, -2, "__metatable");
316 
317  return "Adding formula metatable...\n";
318 }
lua_unit * luaW_pushunit(lua_State *L, Args... args)
Definition: lua_unit.hpp:116
bool luaW_tovconfig(lua_State *L, int index, vconfig &vcfg)
Gets an optional vconfig from either a table or a userdata.
Definition: lua_common.cpp:937
virtual const unit_map & units() const override
Definition: game_board.hpp:113
bool is_map() const
Definition: variant.hpp:68
void luaW_pushteam(lua_State *L, team &tm)
Create a full userdata containing a pointer to the team.
Definition: lua_team.cpp:341
This class represents a single unit of a specific type.
Definition: unit.hpp:133
int luaW_type_error(lua_State *L, int narg, const char *tname)
int as_int() const
Definition: variant.cpp:294
static int impl_formula_tostring(lua_State *L)
bool is_string() const
Definition: variant.hpp:67
bool is_callable() const
Definition: variant.hpp:65
void get_inputs(formula_input_vector &inputs) const
std::vector< formula_input > formula_input_vector
const std::map< variant, variant > & as_map() const
Definition: variant.cpp:333
bool is_list() const
Definition: variant.hpp:66
void luaW_pushunittype(lua_State *L, const unit_type &ut)
Create a lua object containing a reference to a unittype, and a metatable to access the properties...
A single unit type that the player may recruit.
Definition: types.hpp:45
const std::vector< variant > & as_list() const
Definition: variant.cpp:327
bool is_decimal() const
Definition: variant.hpp:64
bool is_int() const
Definition: variant.hpp:63
const std::string & as_string() const
Definition: variant.cpp:321
int intf_eval_formula(lua_State *)
Evaluates a formula in the formula engine.
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:75
int as_decimal() const
Returns variant&#39;s internal representation of decimal number: ie, 1.234 is represented as 1234...
Definition: variant.cpp:303
fwrapper(const std::string &code, wfl::function_symbol_table *functions=nullptr)
variant luaW_tofaivariant(lua_State *L, int i)
unit * luaW_tounit(lua_State *L, int index, bool only_on_map)
Converts a Lua value to a unit pointer.
Definition: lua_unit.cpp:142
variant get_value(const std::string &key) const
game_board * gameboard
Definition: resources.cpp:21
wfl::variant evaluate(const wfl::formula_callable &variables, wfl::formula_debugger *fdb=nullptr) const
config get_parsed_config() const
Definition: variable.cpp:176
int intf_compile_formula(lua_State *)
lua_State * mState
const unit_type * luaW_tounittype(lua_State *L, int idx)
Test if a stack element is a unit type, and return it if so.
Encapsulates the map of the game.
Definition: location.hpp:38
static int impl_formula_collect(lua_State *L)
const_formula_callable_ptr as_callable() const
Definition: variant.hpp:83
unit_iterator find(std::size_t id)
Definition: map.cpp:301
bool luaW_totstring(lua_State *L, int index, t_string &str)
Converts a scalar to a translatable string.
Definition: lua_common.cpp:613
std::size_t i
Definition: function.cpp:968
std::string register_metatables(lua_State *)
static vconfig unconstructed_vconfig()
This is just a wrapper for the default constructor; it exists for historical reasons and to make it c...
Definition: variable.cpp:152
lua_callable(lua_State *L, int i)
team * luaW_toteam(lua_State *L, int idx)
Test if the top stack element is a team, and if so, return it.
Definition: lua_team.cpp:367
static const char *const id_chars
Definition: formula.hpp:75
bool is_null() const
Functions to test the type of the internal value.
Definition: variant.hpp:62
void luaW_pushweapon(lua_State *L, attack_ptr weapon)
std::shared_ptr< T > try_convert() const
Definition: variant.hpp:90
double t
Definition: astarsearch.cpp:65
Definition: contexts.hpp:44
static const char formulaKey[]
void luaW_pushlocation(lua_State *L, const map_location &ml)
Converts a map location object to a Lua table pushed at the top of the stack.
Definition: lua_common.cpp:733
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1358
A variable-expanding proxy for the config class.
Definition: variable.hpp:44
int side() const
The side this unit belongs to.
Definition: unit.hpp:346
virtual int do_compare(const formula_callable *callable) const
Definition: callable.hpp:146
const std::string & str() const
Definition: tstring.hpp:191
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:34
static map_location::DIRECTION n
std::size_t underlying_id() const
This unit&#39;s unique internal ID.
Definition: unit.hpp:395
int do_compare(const formula_callable *other) const
void luaW_pushfaivariant(lua_State *L, variant val)
bool luaW_tolocation(lua_State *L, int index, map_location &loc)
Converts an optional table or pair of integers to a map location object.
Definition: lua_common.cpp:744
const_attack_ptr luaW_toweapon(lua_State *L, int idx)
std::shared_ptr< formula > formula_ptr
Definition: formula_fwd.hpp:22