The Battle for Wesnoth  1.19.18+dev
lua_wml.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2025
3  by Chris Beck <render787@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "scripting/lua_wml.hpp"
18 #include "scripting/lua_common.hpp"
19 
21 #include "serialization/parser.hpp"
23 #include "variable.hpp" // for config_variable_set
24 
25 #include <fstream>
26 
27 #include "lua/wrapper_lauxlib.h"
28 
29 namespace lua_wml {
30 
31 /**
32 * Dumps a wml table or userdata wml object into a pretty string.
33 * - Arg 1: wml table or vconfig userdata
34 * - Ret 1: string
35 */
36 static int intf_wml_tostring(lua_State* L) {
37  const config& arg = luaW_checkconfig(L, 1);
38  std::ostringstream stream;
39  io::write(stream, arg);
40  lua_pushstring(L, stream.str().c_str());
41  return 1;
42 }
43 
44 /**
45  * Loads a WML file into a config
46  * - Arg 1: WML file path
47  * - Arg 2: (optional) Array of preprocessor defines, or false to skip preprocessing (true is also valid)
48  * - Arg 3: (optional) Path to a schema file for validation (omit for no validation)
49  * - Ret: config
50  */
51 static int intf_load_wml(lua_State* L)
52 {
53  std::string file = luaL_checkstring(L, 1);
54  bool preprocess = true;
55  preproc_map defines_map;
56  if(lua_type(L, 2) == LUA_TBOOLEAN) {
57  preprocess = luaW_toboolean(L, 2);
58  } else if(lua_type(L, 2) == LUA_TTABLE || lua_type(L, 2) == LUA_TUSERDATA) {
59  lua_len(L, 2);
60  int n = lua_tointeger(L, -1);
61  lua_pop(L, 1);
62  for(int i = 0; i < n; i++) {
63  const auto arg = scoped_lua_argument{L, 2, i};
64  if(!lua_isstring(L, -1)) {
65  return luaL_argerror(L, 2, "expected bool or array of strings");
66  }
67  std::string define = lua_tostring(L, -1);
68  if(!define.empty()) {
69  defines_map.try_emplace(define, define);
70  }
71  }
72  } else if(!lua_isnoneornil(L, 2)) {
73  return luaL_argerror(L, 2, "expected bool or array of strings");
74  }
75  std::string schema_path = luaL_optstring(L, 3, "");
76  std::shared_ptr<schema_validation::schema_validator> validator;
77  if(!schema_path.empty()) {
79  validator->set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
80  }
81  std::string wml_file = filesystem::get_wml_location(file).value();
83  if(preprocess) {
84  stream = preprocess_file(wml_file, defines_map);
85  } else {
86  stream.reset(new std::ifstream(wml_file));
87  }
88  config result = io::read(*stream, validator.get());
89  luaW_pushconfig(L, result);
90  return 1;
91 }
92 
93 /**
94  * Parses a WML string into a config; does not preprocess or validate
95  * - Arg 1: WML string
96  * - Ret: config
97  */
98 static int intf_parse_wml(lua_State* L)
99 {
100  std::string wml = luaL_checkstring(L, 1);
101  std::string schema_path = luaL_optstring(L, 2, "");
102  std::shared_ptr<schema_validation::schema_validator> validator;
103  if(!schema_path.empty()) {
105  validator->set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
106  }
107  config result = io::read(wml, validator.get());
108  luaW_pushconfig(L, result);
109  return 1;
110 }
111 
112 /**
113  * Returns a clone (deep copy) of the passed config, which can be either a normal config or a vconfig
114  * If it is a vconfig, the underlying config is also cloned.
115  * - Arg 1: a config
116  * - Ret: the cloned config
117  */
118 static int intf_clone_wml(lua_State* L)
119 {
120  const vconfig* vcfg = nullptr;
121  const config& cfg = luaW_checkconfig(L, 1, vcfg);
122  if(vcfg) {
123  config clone_underlying = vcfg->get_config();
124  vconfig clone(clone_underlying);
125  luaW_pushvconfig(L, clone);
126  } else {
127  luaW_pushconfig(L, cfg);
128  }
129  return 1;
130 }
131 
132 /**
133 * Interpolates variables into a WML table, including [insert_tag]
134 * Arg 1: WML table to interpolate into
135 * Arg 2: WML table of variables
136 */
137 static int intf_wml_interpolate(lua_State* L)
138 {
139  config cfg = luaW_checkconfig(L, 1), vars_cfg = luaW_checkconfig(L, 2);
140  config_variable_set vars(vars_cfg);
141  vconfig vcfg(cfg, vars);
143  return 1;
144 }
145 
146 /**
147 * Tests if a WML table matches a filter
148 * Arg 1: table to test
149 * Arg 2: filter
150 */
151 static int intf_wml_matches_filter(lua_State* L)
152 {
153  config cfg = luaW_checkconfig(L, 1);
155  lua_pushboolean(L, cfg.matches(filter));
156  return 1;
157 }
158 
159 /**
160 * Merges two WML tables
161 * Arg 1: base table
162 * Arg 2: table to merge in
163 */
164 static int intf_wml_merge(lua_State* L)
165 {
166  config base = luaW_checkconfig(L, 1);
167  config merge = luaW_checkconfig(L, 2);
168  const std::string mode = lua_isstring(L, 3) ? luaL_checkstring(L, 3) : "merge";
169  if(mode == "append") {
170  base.merge_attributes(merge);
171  base.append_children(merge);
172  } else {
173  if(mode == "replace") {
174  for(const auto [key, _] : merge.all_children_view()) {
175  base.clear_children(key);
176  }
177  } else if(mode != "merge") {
178  return luaL_argerror(L, 3, "invalid merge mode - must be merge, append, or replace");
179  }
180  base.merge_with(merge);
181  }
182  luaW_pushconfig(L, base);
183  return 1;
184 }
185 
186 /**
187 * Computes a diff of two WML tables
188 * Arg 1: left table
189 * Arg 2: right table
190 */
191 static int intf_wml_diff(lua_State* L)
192 {
193  config lhs = luaW_checkconfig(L, 1);
194  config rhs = luaW_checkconfig(L, 2);
195  luaW_pushconfig(L, lhs.get_diff(rhs));
196  return 1;
197 }
198 
199 /**
200 * Applies a diff to a WML table
201 * Arg 1: base table
202 * Arg 2: WML diff
203 */
204 static int intf_wml_patch(lua_State* L)
205 {
206  config base = luaW_checkconfig(L, 1);
207  config patch = luaW_checkconfig(L, 2);
208  base.apply_diff(patch);
209  luaW_pushconfig(L, base);
210  return 1;
211 }
212 
213 /**
214 * Tests if two WML tables are equal (have the same keys and values, same tags, recursively)
215 * Arg 1: left table
216 * Arg 2: right table
217 */
218 static int intf_wml_equal(lua_State* L)
219 {
220  config left = luaW_checkconfig(L, 1);
221  config right = luaW_checkconfig(L, 2);
222  lua_pushboolean(L, left == right);
223  return 1;
224 }
225 
226 /**
227 * Tests if a table represents a valid WML table
228 * Arg 1: table
229 */
230 static int intf_wml_valid(lua_State* L)
231 {
232  config test;
233  if(luaW_toconfig(L, 1, test)) {
234  // The validate_wml call is PROBABLY redundant, but included just in case validation changes and toconfig isn't updated to match
235  lua_pushboolean(L, test.validate_wml());
236  } else lua_pushboolean(L, false);
237  return 1;
238 }
239 
240 int luaW_open(lua_State* L) {
241  auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
242  lk.add_log("Adding wml module...\n");
243  static luaL_Reg const wml_callbacks[]= {
244  { "load", &intf_load_wml},
245  { "parse", &intf_parse_wml},
246  { "clone", &intf_clone_wml},
247  { "merge", &intf_wml_merge},
248  { "diff", &intf_wml_diff},
249  { "patch", &intf_wml_patch},
250  { "equal", &intf_wml_equal},
251  { "valid", &intf_wml_valid},
252  { "matches_filter", &intf_wml_matches_filter},
253  { "tostring", &intf_wml_tostring},
254  { "interpolate", &intf_wml_interpolate},
255  { nullptr, nullptr },
256  };
257  lua_newtable(L);
258  luaL_setfuncs(L, wml_callbacks, 0);
259  return 1;
260 }
261 
262 }
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
bool matches(const config &filter) const
Definition: config.cpp:1168
bool validate_wml() const
Returns true if this object represents valid WML, i.e.
Definition: config.cpp:1292
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:795
void merge_attributes(const config &)
Definition: config.cpp:722
void clear_children(T... keys)
Definition: config.hpp:601
void merge_with(const config &c)
Merge config 'c' into this config, overwriting this config's values.
Definition: config.cpp:1097
void apply_diff(const config &diff, bool track=false)
A function to apply a diff config onto this config object.
Definition: config.cpp:1000
config get_diff(const config &c) const
A function to get the differences between this object, and 'c', as another config object.
Definition: config.cpp:884
void append_children(const config &cfg)
Adds children from cfg.
Definition: config.cpp:167
Realization of serialization/validator.hpp abstract validator.
Shallow wrapper around lua_geti which pops the top variable from the Lua stack when destroyed.
Definition: lua_common.hpp:53
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
const config & get_config() const
Definition: variable.hpp:75
config get_parsed_config() const
Definition: variable.cpp:177
const config * cfg
std::size_t i
Definition: function.cpp:1032
static std::string _(const char *str)
Definition: gettext.hpp:97
void luaW_pushconfig(lua_State *L, const config &cfg)
Converts a config object to a Lua table pushed at the top of the stack.
Definition: lua_common.cpp:864
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:971
void luaW_pushvconfig(lua_State *L, const vconfig &cfg)
Pushes a vconfig on the top of the stack.
Definition: lua_common.cpp:499
bool luaW_toboolean(lua_State *L, int n)
bool luaW_toconfig(lua_State *L, int index, config &cfg)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:893
utils::optional< std::string > get_wml_location(const std::string &path, const utils::optional< std::string > &current_dir)
Returns a translated path to the actual file or directory, if it exists.
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:53
config read(std::istream &in, abstract_validator *validator)
Definition: parser.cpp:600
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:737
static int intf_wml_patch(lua_State *L)
Applies a diff to a WML table Arg 1: base table Arg 2: WML diff.
Definition: lua_wml.cpp:204
static int intf_wml_matches_filter(lua_State *L)
Tests if a WML table matches a filter Arg 1: table to test Arg 2: filter.
Definition: lua_wml.cpp:151
static int intf_wml_tostring(lua_State *L)
Dumps a wml table or userdata wml object into a pretty string.
Definition: lua_wml.cpp:36
static int intf_wml_equal(lua_State *L)
Tests if two WML tables are equal (have the same keys and values, same tags, recursively) Arg 1: left...
Definition: lua_wml.cpp:218
int luaW_open(lua_State *L)
Definition: lua_wml.cpp:240
static int intf_wml_valid(lua_State *L)
Tests if a table represents a valid WML table Arg 1: table.
Definition: lua_wml.cpp:230
static int intf_load_wml(lua_State *L)
Loads a WML file into a config.
Definition: lua_wml.cpp:51
static int intf_wml_interpolate(lua_State *L)
Interpolates variables into a WML table, including [insert_tag] Arg 1: WML table to interpolate into ...
Definition: lua_wml.cpp:137
static int intf_parse_wml(lua_State *L)
Parses a WML string into a config; does not preprocess or validate.
Definition: lua_wml.cpp:98
static int intf_wml_diff(lua_State *L)
Computes a diff of two WML tables Arg 1: left table Arg 2: right table.
Definition: lua_wml.cpp:191
static int intf_clone_wml(lua_State *L)
Returns a clone (deep copy) of the passed config, which can be either a normal config or a vconfig If...
Definition: lua_wml.cpp:118
static int intf_wml_merge(lua_State *L)
Merges two WML tables Arg 1: base table Arg 2: table to merge in.
Definition: lua_wml.cpp:164
constexpr auto filter
Definition: ranges.hpp:38
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map &defines)
Function to use the WML preprocessor on a file.
std::map< std::string, struct preproc_define > preproc_map
One of the realizations of serialization/validator.hpp abstract validator.
static map_location::direction n