
These will be changed to conditionally include system Lua headers, e.g. "lua.h", instead of submodule Lua headers, e.g. "module/lua/lua.h". If a header named "lua.h" includes "lua.h", the build will fail due to recursion. This can't be solved using angle brackets to include system headers, because macos builds won't find them: In file included from /Users/runner/work/wesnoth/wesnoth/src/ai/registry.cpp:30: In file included from /Users/runner/work/wesnoth/wesnoth/src/ai/composite/aspect.hpp:24: In file included from /Users/runner/work/wesnoth/wesnoth/src/ai/lua/lua_object.hpp:25: /Users/runner/work/wesnoth/wesnoth/src/lua/lua.h:4:14: error: 'lua.h' file not found with <angled> include; use "quotes" instead #include <lua.h> ^~~~~~~ "lua.h" Renamed with (requires GNU sed): $ for f in src/lua/*.h; do > git mv "${f}" "src/lua/wrapper_${f#src/lua/}"; > done $ git grep -El -- '#[ \t]*include[ \t]+"lua/[^"]+[.]h"' src | \ > xargs sed -Ei -- ' > s|(#[ \t]*include[ \t]+"lua/)(lua[.]h")( )?|\1wrapper_\2|; > s|(#[ \t]*include[ \t]+"lua/)(lualib[.]h")( )?|\1wrapper_\2|; > s|(#[ \t]*include[ \t]+"lua/)(lauxlib[.]h")( )?|\1wrapper_\2|; > '
318 lines
9.8 KiB
C++
318 lines
9.8 KiB
C++
/*
|
|
Copyright (C) 2017 - 2024
|
|
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_formula_bridge.hpp"
|
|
|
|
#include "game_board.hpp"
|
|
#include "scripting/game_lua_kernel.hpp"
|
|
#include "scripting/lua_unit.hpp"
|
|
#include "scripting/lua_common.hpp"
|
|
#include "scripting/lua_team.hpp"
|
|
#include "scripting/lua_unit_attacks.hpp"
|
|
#include "scripting/lua_unit_type.hpp"
|
|
#include "lua/wrapper_lauxlib.h"
|
|
#include "formula/callable_objects.hpp"
|
|
#include "formula/formula.hpp"
|
|
#include "variable.hpp"
|
|
|
|
#include "resources.hpp"
|
|
#include "units/map.hpp"
|
|
#include "units/unit.hpp"
|
|
|
|
static const char formulaKey[] = "formula";
|
|
|
|
using namespace wfl;
|
|
|
|
void luaW_pushfaivariant(lua_State* L, variant val);
|
|
variant luaW_tofaivariant(lua_State* L, int i);
|
|
|
|
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;
|
|
std::size_t n = lua_rawlen(mState, table_i);
|
|
if(n == 0) {
|
|
return variant();
|
|
}
|
|
for(std::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(formula_input_vector& inputs) const {
|
|
add_input(inputs, "__list");
|
|
add_input(inputs, "__map");
|
|
for(lua_pushnil(mState); lua_next(mState, table_i); lua_pop(mState,1)) {
|
|
lua_pushvalue(mState, -2);
|
|
bool is_valid_key = (lua_type(mState, -1) == LUA_TSTRING) && !lua_isnumber(mState, -1);
|
|
lua_pop(mState, 1);
|
|
if(is_valid_key) {
|
|
std::string key = lua_tostring(mState, -2);
|
|
if(key.find_first_not_of(formula::id_chars) != std::string::npos) {
|
|
add_input(inputs, key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
int do_compare(const formula_callable* other) const {
|
|
const lua_callable* lua = dynamic_cast<const lua_callable*>(other);
|
|
if(lua == nullptr) {
|
|
return formula_callable::do_compare(other);
|
|
}
|
|
if(mState == lua->mState) { // Which should always be the case, but let's be safe here
|
|
if(lua_compare(mState, table_i, lua->table_i, LUA_OPEQ)) {
|
|
return 0;
|
|
}
|
|
int top = lua_gettop(mState);
|
|
if(lua_getmetatable(mState, table_i)) {
|
|
lua_getfield(mState, -1, "__lt");
|
|
if(!lua_isnoneornil(mState, -1)) {
|
|
if(lua_getmetatable(mState, lua->table_i)) {
|
|
lua_getfield(mState, -1, "__lt");
|
|
if(!lua_isnoneornil(mState, -1)) {
|
|
lua_settop(mState, top);
|
|
return lua_compare(mState, table_i, lua->table_i, LUA_OPLT) ? -1 : 1;
|
|
}
|
|
if(lua_compare(mState, -4, -2, LUA_OPEQ)) {
|
|
lua_settop(mState, top);
|
|
return 0;
|
|
}
|
|
const void* lhs = lua_topointer(mState, -4);
|
|
const void* rhs = lua_topointer(mState, -2);
|
|
lua_settop(mState, top);
|
|
return lhs < rhs ? -1 : (lhs > rhs ? 1 : 0);
|
|
}
|
|
}
|
|
}
|
|
lua_settop(mState, top);
|
|
return lua_topointer(mState, -2) < lua_topointer(mState, -1) ? -1 : 1;
|
|
}
|
|
return mState < lua->mState ? -1 : 1;
|
|
}
|
|
};
|
|
|
|
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
|
|
if(auto u_ref = val.try_convert<unit_callable>()) {
|
|
const unit& u = u_ref->get_unit();
|
|
unit_map::iterator un_it = resources::gameboard->units().find(u.get_location());
|
|
if(&*un_it == &u) {
|
|
luaW_pushunit(L, u.underlying_id());
|
|
} else {
|
|
luaW_pushunit(L, u.side(), u.underlying_id());
|
|
}
|
|
} else if(auto ut_ref = val.try_convert<unit_type_callable>()) {
|
|
const unit_type& ut = ut_ref->get_unit_type();
|
|
luaW_pushunittype(L, ut);
|
|
} else if(auto atk_ref = val.try_convert<attack_type_callable>()) {
|
|
const auto& atk = atk_ref->get_attack_type();
|
|
luaW_pushweapon(L, atk.shared_from_this());
|
|
} else if(auto team_ref = val.try_convert<team_callable>()) {
|
|
auto t = team_ref->get_team();
|
|
luaW_pushteam(L, t);
|
|
} else if(auto loc_ref = val.try_convert<location_callable>()) {
|
|
luaW_pushlocation(L, loc_ref->loc());
|
|
} else {
|
|
// If those fail, convert generically to a map
|
|
auto obj = val.as_callable();
|
|
formula_input_vector inputs;
|
|
obj->get_inputs(inputs);
|
|
lua_newtable(L);
|
|
for(const formula_input& attr : inputs) {
|
|
if(attr.access == formula_access::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(std::make_shared<lua_callable>(L, i));
|
|
case LUA_TUSERDATA:
|
|
static t_string tstr;
|
|
static vconfig vcfg = vconfig::unconstructed_vconfig();
|
|
static map_location loc;
|
|
if(luaW_totstring(L, i, tstr)) {
|
|
return variant(tstr.str());
|
|
} else if(luaW_tovconfig(L, i, vcfg)) {
|
|
return variant(std::make_shared<config_callable>(vcfg.get_parsed_config()));
|
|
} else if(unit* u = luaW_tounit(L, i)) {
|
|
return variant(std::make_shared<unit_callable>(*u));
|
|
} else if(const unit_type* ut = luaW_tounittype(L, i)) {
|
|
return variant(std::make_shared<unit_type_callable>(*ut));
|
|
} else if(const_attack_ptr atk = luaW_toweapon(L, i)) {
|
|
return variant(std::make_shared<attack_type_callable>(*atk));
|
|
} else if(team* t = luaW_toteam(L, i)) {
|
|
return variant(std::make_shared<team_callable>(*t));
|
|
} else if(luaW_tolocation(L, i, loc)) {
|
|
return variant(std::make_shared<location_callable>(loc));
|
|
}
|
|
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)
|
|
{
|
|
bool need_delete = false;
|
|
fwrapper* form;
|
|
if(void* ud = luaL_testudata(L, 1, formulaKey)) {
|
|
form = static_cast<fwrapper*>(ud);
|
|
} else {
|
|
need_delete = true;
|
|
form = new fwrapper(luaL_checkstring(L, 1));
|
|
}
|
|
std::shared_ptr<formula_callable> context, fallback;
|
|
if(unit* u = luaW_tounit(L, 2)) {
|
|
context.reset(new unit_callable(*u));
|
|
} else if(const unit_type* ut = luaW_tounittype(L, 2)) {
|
|
context.reset(new unit_type_callable(*ut));
|
|
} else if(const_attack_ptr atk = luaW_toweapon(L, 2)) {
|
|
context.reset(new attack_type_callable(*atk));
|
|
} else if(team* t = luaW_toteam(L, 2)) {
|
|
context.reset(new team_callable(*t));
|
|
} 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);
|
|
if(need_delete) {
|
|
delete form;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int lua_formula_bridge::intf_compile_formula(lua_State* L)
|
|
{
|
|
if(!lua_isstring(L, 1)) {
|
|
luaW_type_error(L, 1, "string");
|
|
}
|
|
new(L) fwrapper(lua_tostring(L, 1));
|
|
luaL_setmetatable(L, formulaKey);
|
|
return 1;
|
|
}
|
|
|
|
lua_formula_bridge::fwrapper::fwrapper(const std::string& code, function_symbol_table* functions)
|
|
: formula_ptr(new formula(code, functions))
|
|
{
|
|
}
|
|
|
|
std::string lua_formula_bridge::fwrapper::str() const
|
|
{
|
|
if(formula_ptr) {
|
|
return formula_ptr->str();
|
|
}
|
|
return "";
|
|
}
|
|
|
|
variant lua_formula_bridge::fwrapper::evaluate(const formula_callable& variables, formula_debugger* fdb) const
|
|
{
|
|
if(formula_ptr) {
|
|
return formula_ptr->evaluate(variables, fdb);
|
|
}
|
|
return variant();
|
|
}
|
|
|
|
static int impl_formula_collect(lua_State* L)
|
|
{
|
|
lua_formula_bridge::fwrapper* form = static_cast<lua_formula_bridge::fwrapper*>(lua_touserdata(L, 1));
|
|
form->~fwrapper();
|
|
return 0;
|
|
}
|
|
|
|
static int impl_formula_tostring(lua_State* L)
|
|
{
|
|
lua_formula_bridge::fwrapper* form = static_cast<lua_formula_bridge::fwrapper*>(lua_touserdata(L, 1));
|
|
const std::string str = form->str();
|
|
lua_pushlstring(L, str.c_str(), str.size());
|
|
return 1;
|
|
}
|
|
|
|
std::string lua_formula_bridge::register_metatables(lua_State* L)
|
|
{
|
|
luaL_newmetatable(L, formulaKey);
|
|
lua_pushcfunction(L, impl_formula_collect);
|
|
lua_setfield(L, -2, "__gc");
|
|
lua_pushcfunction(L, impl_formula_tostring);
|
|
lua_setfield(L, -2, "__tostring");
|
|
lua_pushcfunction(L, intf_eval_formula);
|
|
lua_setfield(L, -2, "__call");
|
|
lua_pushstring(L, "formula");
|
|
lua_setfield(L, -2, "__metatable");
|
|
|
|
return "Adding formula metatable...\n";
|
|
}
|