The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
lua_formula_bridge.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2017 - 2025
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"
23#include "lua/wrapper_lauxlib.h"
25#include "formula/formula.hpp"
26#include "variable.hpp"
27
28#include "resources.hpp"
29#include "units/map.hpp"
30#include "units/unit.hpp"
31
32static const char formulaKey[] = "formula";
33
34using namespace wfl;
35
36void luaW_pushfaivariant(lua_State* L, const variant& val);
37variant luaW_tofaivariant(lua_State* L, int i);
38
40 lua_State* mState;
42public:
43 lua_callable(lua_State* L, int i) : mState(L), table_i(lua_absindex(L,i)) {}
44 variant get_value(const std::string& key) const {
45 if(key == "__list") {
46 std::vector<variant> values;
47 std::size_t n = lua_rawlen(mState, table_i);
48 if(n == 0) {
49 return variant();
50 }
51 for(std::size_t i = 1; i <= n; i++) {
52 lua_pushinteger(mState, i);
53 lua_gettable(mState, table_i);
54 values.push_back(luaW_tofaivariant(mState, -1));
55 }
56 return variant(std::move(values));
57 } else if(key == "__map") {
58 std::map<variant, variant> values;
59 for(lua_pushnil(mState); lua_next(mState, table_i); lua_pop(mState, 1)) {
61 }
62 return variant(std::move(values));
63 }
64 lua_pushlstring(mState, key.c_str(), key.size());
65 lua_gettable(mState, table_i);
66 variant result = luaW_tofaivariant(mState, -1);
67 lua_pop(mState, 1);
68 return result;
69 }
71 add_input(inputs, "__list");
72 add_input(inputs, "__map");
73 for(lua_pushnil(mState); lua_next(mState, table_i); lua_pop(mState,1)) {
74 lua_pushvalue(mState, -2);
75 bool is_valid_key = (lua_type(mState, -1) == LUA_TSTRING) && !lua_isnumber(mState, -1);
76 lua_pop(mState, 1);
77 if(is_valid_key) {
78 std::string key = lua_tostring(mState, -2);
79 if(key.find_first_not_of(formula::id_chars) != std::string::npos) {
80 add_input(inputs, key);
81 }
82 }
83 }
84 }
85 int do_compare(const formula_callable* other) const {
86 const lua_callable* lua = dynamic_cast<const lua_callable*>(other);
87 if(lua == nullptr) {
88 return formula_callable::do_compare(other);
89 }
90 if(mState == lua->mState) { // Which should always be the case, but let's be safe here
91 if(lua_compare(mState, table_i, lua->table_i, LUA_OPEQ)) {
92 return 0;
93 }
94 int top = lua_gettop(mState);
95 if(lua_getmetatable(mState, table_i)) {
96 lua_getfield(mState, -1, "__lt");
97 if(!lua_isnoneornil(mState, -1)) {
98 if(lua_getmetatable(mState, lua->table_i)) {
99 lua_getfield(mState, -1, "__lt");
100 if(!lua_isnoneornil(mState, -1)) {
101 lua_settop(mState, top);
102 return lua_compare(mState, table_i, lua->table_i, LUA_OPLT) ? -1 : 1;
103 }
104 if(lua_compare(mState, -4, -2, LUA_OPEQ)) {
105 lua_settop(mState, top);
106 return 0;
107 }
108 const void* lhs = lua_topointer(mState, -4);
109 const void* rhs = lua_topointer(mState, -2);
110 lua_settop(mState, top);
111 return lhs < rhs ? -1 : (lhs > rhs ? 1 : 0);
112 }
113 }
114 }
115 lua_settop(mState, top);
116 return lua_topointer(mState, -2) < lua_topointer(mState, -1) ? -1 : 1;
117 }
118 return mState < lua->mState ? -1 : 1;
119 }
120};
121
122void luaW_pushfaivariant(lua_State* L, const variant& val) {
123 if(val.is_int()) {
124 lua_pushinteger(L, val.as_int());
125 } else if(val.is_decimal()) {
126 lua_pushnumber(L, val.as_decimal() / 1000.0);
127 } else if(val.is_string()) {
128 const std::string result_string = val.as_string();
129 lua_pushlstring(L, result_string.c_str(), result_string.size());
130 } else if(val.is_list()) {
131 lua_newtable(L);
132 for(const variant& v : val.as_list()) {
133 lua_pushinteger(L, lua_rawlen(L, -1) + 1);
135 lua_settable(L, -3);
136 }
137 } else if(val.is_map()) {
138 typedef std::map<variant, variant>::value_type kv_type;
139 lua_newtable(L);
140 for(const kv_type& v : val.as_map()) {
141 luaW_pushfaivariant(L, v.first);
142 luaW_pushfaivariant(L, v.second);
143 lua_settable(L, -3);
144 }
145 } else if(val.is_callable()) {
146 // First try a few special cases
147 if(auto u_ref = callable_cast<unit_callable*>(val)) {
148 const unit& u = u_ref->get_unit();
150 if(&*un_it == &u) {
152 } else {
153 luaW_pushunit(L, u.side(), u.underlying_id());
154 }
155 } else if(auto ut_ref = callable_cast<unit_type_callable*>(val)) {
156 const unit_type& ut = ut_ref->get_unit_type();
157 luaW_pushunittype(L, ut);
158 } else if(auto atk_ref = callable_cast<attack_type_callable*>(val)) {
159 const auto& atk = atk_ref->get_attack_type();
160 luaW_pushweapon(L, atk.shared_from_this());
161 } else if(auto team_ref = callable_cast<team_callable*>(val)) {
162 auto& t = team_ref->get_team();
163 luaW_pushteam(L, const_cast<team&>(t));
164 } else if(auto loc_ref = callable_cast<location_callable*>(val)) {
165 luaW_pushlocation(L, loc_ref->loc());
166 } else {
167 // If those fail, convert generically to a map
168 auto obj = val.as_callable();
170 obj->get_inputs(inputs);
171 lua_newtable(L);
172 for(const formula_input& attr : inputs) {
173 if(attr.access == formula_access::write_only) {
174 continue;
175 }
176 lua_pushstring(L, attr.name.c_str());
177 luaW_pushfaivariant(L, obj->query_value(attr.name));
178 lua_settable(L, -3);
179 }
180 }
181 } else if(val.is_null()) {
182 lua_pushnil(L);
183 }
184}
185
186variant luaW_tofaivariant(lua_State* L, int i) {
187 switch(lua_type(L, i)) {
188 case LUA_TBOOLEAN:
189 return variant(lua_tointeger(L, i));
190 case LUA_TNUMBER:
191 return variant(lua_tonumber(L, i), variant::DECIMAL_VARIANT);
192 case LUA_TSTRING:
193 return variant(lua_tostring(L, i));
194 case LUA_TTABLE:
195 return make_callable<lua_callable>(L, i);
196 case LUA_TUSERDATA:
197 static t_string tstr;
199 static map_location loc;
200 if(luaW_totstring(L, i, tstr)) {
201 return variant(tstr.str());
202 } else if(luaW_tovconfig(L, i, vcfg)) {
203 return make_callable<config_callable>(vcfg.get_parsed_config());
204 } else if(unit* u = luaW_tounit(L, i)) {
205 return make_callable<unit_callable>(*u);
206 } else if(const unit_type* ut = luaW_tounittype(L, i)) {
207 return make_callable<unit_type_callable>(*ut);
208 } else if(const_attack_ptr atk = luaW_toweapon(L, i)) {
209 return make_callable<attack_type_callable>(*atk);
210 } else if(team* t = luaW_toteam(L, i)) {
211 return make_callable<team_callable>(*t);
212 } else if(luaW_tolocation(L, i, loc)) {
213 return make_callable<location_callable>(loc);
214 }
215 break;
216 }
217 return variant();
218}
219
220/**
221 * Get a formula from the stack. If @a allow_str is true, it compiles a formula string if found.
222 * Otherwise, it must be an already-compiled formula.
223 * Raises an error if a formula is not found, or if there's an error in compilation.
224 * Thus, it never returns a null pointer.
225 */
226lua_formula_bridge::fpointer luaW_check_formula(lua_State* L, int idx, bool allow_str) {
227 using namespace lua_formula_bridge;
228 fpointer form;
229 if(void* ud = luaL_testudata(L, idx, formulaKey)) {
230 form.get_deleter() = [](fwrapper*) {};
231 form.reset(static_cast<fwrapper*>(ud));
232 // Setting a no-op deleter guarantees the Lua-held object is not deleted
233 } else if(allow_str) {
234 form.get_deleter() = std::default_delete<fwrapper>();
235 form.reset(new fwrapper(luaL_checkstring(L, idx)));
236 // Set deleter to default so it's deleted properly later
237 } else {
238 luaW_type_error(L, idx, "formula");
239 }
240 return form;
241}
242
243/**
244 * Evaluates a formula in the formula engine.
245 * - Arg 1: Formula string.
246 * - Arg 2: optional context; can be a unit or a Lua table.
247 * - Ret 1: Result of the formula.
248 */
250{
251 fpointer form = luaW_check_formula(L, 1, true);
252 std::shared_ptr<formula_callable> context, fallback;
253 if(unit* u = luaW_tounit(L, 2)) {
254 context.reset(new unit_callable(*u));
255 } else if(const unit_type* ut = luaW_tounittype(L, 2)) {
256 context.reset(new unit_type_callable(*ut));
257 } else if(const_attack_ptr atk = luaW_toweapon(L, 2)) {
258 context.reset(new attack_type_callable(*atk));
259 } else if(team* t = luaW_toteam(L, 2)) {
260 context.reset(new team_callable(*t));
261 } else if(lua_istable(L, 2)) {
262 context.reset(new lua_callable(L, 2));
263 } else {
264 context.reset(new map_formula_callable);
265 }
266 variant result = form->evaluate(*context);
267 luaW_pushfaivariant(L, result);
268 return 1;
269}
270
272{
273 if(!lua_isstring(L, 1)) {
274 luaW_type_error(L, 1, "string");
275 }
276 new(L) fwrapper(lua_tostring(L, 1));
277 luaL_setmetatable(L, formulaKey);
278 return 1;
279}
280
282 : formula_ptr(new formula(code))
283{
284}
285
287{
288 if(formula_ptr) {
289 return formula_ptr->str();
290 }
291 return "";
292}
293
295{
296 if(formula_ptr) {
297 return formula_ptr->evaluate(variables, fdb);
298 }
299 return variant();
300}
301
302static int impl_formula_collect(lua_State* L)
303{
304 lua_formula_bridge::fwrapper* form = static_cast<lua_formula_bridge::fwrapper*>(luaL_checkudata(L, 1, formulaKey));
305 form->~fwrapper();
306 return 0;
307}
308
309static int impl_formula_tostring(lua_State* L)
310{
311 lua_formula_bridge::fwrapper* form = static_cast<lua_formula_bridge::fwrapper*>(luaL_checkudata(L, 1, formulaKey));
312 const std::string str = form->str();
313 lua_pushlstring(L, str.c_str(), str.size());
314 return 1;
315}
316
318{
319 luaL_newmetatable(L, formulaKey);
320 lua_pushcfunction(L, impl_formula_collect);
321 lua_setfield(L, -2, "__gc");
322 lua_pushcfunction(L, impl_formula_tostring);
323 lua_setfield(L, -2, "__tostring");
324 lua_pushcfunction(L, intf_eval_formula);
325 lua_setfield(L, -2, "__call");
326 lua_pushstring(L, "formula");
327 lua_setfield(L, -2, "__metatable");
328
329 return "Adding formula metatable...\n";
330}
map_location loc
Definition move.cpp:172
double t
virtual const unit_map & units() const override
int do_compare(const formula_callable *other) const
void get_inputs(formula_input_vector &inputs) const
variant get_value(const std::string &key) const
lua_callable(lua_State *L, int i)
fwrapper(const std::string &code)
wfl::variant evaluate(const wfl::formula_callable &variables, wfl::formula_debugger *fdb=nullptr) const
const std::string & str() const
Definition tstring.hpp:204
This class stores all the data for a single 'side' (in game nomenclature).
Definition team.hpp:74
unit_iterator find(std::size_t id)
Definition map.cpp:302
A single unit type that the player may recruit.
Definition types.hpp:43
This class represents a single unit of a specific type.
Definition unit.hpp:39
A variable-expanding proxy for the config class.
Definition variable.hpp:45
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:153
config get_parsed_config() const
Definition variable.cpp:177
formula_input_vector inputs() const
Definition callable.hpp:63
static void add_input(formula_input_vector &inputs, const std::string &key, formula_access access_type=formula_access::read_only)
Definition callable.hpp:134
virtual int do_compare(const formula_callable *callable) const
Definition callable.hpp:144
static const char *const id_chars
Definition formula.hpp:83
bool is_map() const
Definition variant.hpp:75
bool is_list() const
Definition variant.hpp:73
int as_int(int fallback=0) const
Returns the variant's value as an integer.
Definition variant.cpp:336
int as_decimal(int fallback=0) const
Returns the variant's internal representation of decimal number: ie, 1.234 is represented as 1234.
Definition variant.cpp:344
const_formula_callable_ptr as_callable() const
Definition variant.cpp:377
bool is_decimal() const
Definition variant.hpp:71
const std::string & as_string() const
Definition variant.cpp:362
bool is_string() const
Definition variant.hpp:74
bool is_callable() const
Definition variant.hpp:72
const std::vector< variant > & as_list() const
Definition variant.cpp:367
const std::map< variant, variant > & as_map() const
Definition variant.cpp:372
bool is_null() const
Functions to test the type of the internal value.
Definition variant.hpp:69
bool is_int() const
Definition variant.hpp:70
int side() const
The side this unit belongs to.
Definition unit.hpp:249
std::size_t underlying_id() const
This unit's unique internal ID.
Definition unit.hpp:298
const map_location & get_location() const
The current map location this unit is at.
Definition unit.hpp:1327
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.
bool luaW_tovconfig(lua_State *L, int index, vconfig &vcfg)
Gets an optional vconfig from either a table or a userdata.
int luaW_type_error(lua_State *L, int narg, const char *tname)
bool luaW_totstring(lua_State *L, int index, t_string &str)
Converts a scalar to a translatable string.
bool luaW_tolocation(lua_State *L, int index, map_location &loc)
Converts an optional table or pair of integers to a map location object.
static const char formulaKey[]
lua_formula_bridge::fpointer luaW_check_formula(lua_State *L, int idx, bool allow_str)
Get a formula from the stack.
variant luaW_tofaivariant(lua_State *L, int i)
static int impl_formula_collect(lua_State *L)
void luaW_pushfaivariant(lua_State *L, const variant &val)
static int impl_formula_tostring(lua_State *L)
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:562
void luaW_pushteam(lua_State *L, team &tm)
Create a full userdata containing a pointer to the team.
Definition lua_team.cpp:536
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
lua_unit * luaW_pushunit(lua_State *L, Args... args)
Definition lua_unit.hpp:116
const_attack_ptr luaW_toweapon(lua_State *L, int idx)
void luaW_pushweapon(lua_State *L, const attack_ptr &weapon)
const unit_type * luaW_tounittype(lua_State *L, int idx)
Test if a stack element is a unit type, and return it if so.
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.
std::unique_ptr< fwrapper, std::function< void(fwrapper *)> > fpointer
std::string register_metatables(lua_State *)
int intf_compile_formula(lua_State *)
int intf_eval_formula(lua_State *)
Evaluates a formula in the formula engine.
game_board * gameboard
Definition resources.cpp:20
std::vector< formula_input > formula_input_vector
std::shared_ptr< formula > formula_ptr
std::shared_ptr< const attack_type > const_attack_ptr
Definition ptr.hpp:34
Encapsulates the map of the game.
Definition location.hpp:46
static map_location::direction n
#define i