The Battle for Wesnoth  1.19.7+dev
lua_wml.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2024
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  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  lua_geti(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  lua_pop(L, 1);
69  if(!define.empty()) {
70  defines_map.emplace(define, preproc_define(define));
71  }
72  }
73  } else if(!lua_isnoneornil(L, 2)) {
74  return luaL_argerror(L, 2, "expected bool or array of strings");
75  }
76  std::string schema_path = luaL_optstring(L, 3, "");
77  std::shared_ptr<schema_validation::schema_validator> validator;
78  if(!schema_path.empty()) {
80  validator->set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
81  }
82  std::string wml_file = filesystem::get_wml_location(file).value();
84  config result;
85  if(preprocess) {
86  stream = preprocess_file(wml_file, &defines_map);
87  } else {
88  stream.reset(new std::ifstream(wml_file));
89  }
90  read(result, *stream, validator.get());
91  luaW_pushconfig(L, result);
92  return 1;
93 }
94 
95 /**
96  * Parses a WML string into a config; does not preprocess or validate
97  * - Arg 1: WML string
98  * - Ret: config
99  */
100 static int intf_parse_wml(lua_State* L)
101 {
102  std::string wml = luaL_checkstring(L, 1);
103  std::string schema_path = luaL_optstring(L, 2, "");
104  std::shared_ptr<schema_validation::schema_validator> validator;
105  if(!schema_path.empty()) {
107  validator->set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
108  }
109  config result;
110  read(result, wml, validator.get());
111  luaW_pushconfig(L, result);
112  return 1;
113 }
114 
115 /**
116  * Returns a clone (deep copy) of the passed config, which can be either a normal config or a vconfig
117  * If it is a vconfig, the underlying config is also cloned.
118  * - Arg 1: a config
119  * - Ret: the cloned config
120  */
121 static int intf_clone_wml(lua_State* L)
122 {
123  const vconfig* vcfg = nullptr;
124  const config& cfg = luaW_checkconfig(L, 1, vcfg);
125  if(vcfg) {
126  config clone_underlying = vcfg->get_config();
127  vconfig clone(clone_underlying);
128  luaW_pushvconfig(L, clone);
129  } else {
130  luaW_pushconfig(L, cfg);
131  }
132  return 1;
133 }
134 
135 /**
136 * Interpolates variables into a WML table, including [insert_tag]
137 * Arg 1: WML table to interpolate into
138 * Arg 2: WML table of variables
139 */
140 static int intf_wml_interpolate(lua_State* L)
141 {
142  config cfg = luaW_checkconfig(L, 1), vars_cfg = luaW_checkconfig(L, 2);
143  config_variable_set vars(vars_cfg);
144  vconfig vcfg(cfg, vars);
146  return 1;
147 }
148 
149 /**
150 * Tests if a WML table matches a filter
151 * Arg 1: table to test
152 * Arg 2: filter
153 */
154 static int intf_wml_matches_filter(lua_State* L)
155 {
156  config cfg = luaW_checkconfig(L, 1);
157  config filter = luaW_checkconfig(L, 2);
158  lua_pushboolean(L, cfg.matches(filter));
159  return 1;
160 }
161 
162 /**
163 * Merges two WML tables
164 * Arg 1: base table
165 * Arg 2: table to merge in
166 */
167 static int intf_wml_merge(lua_State* L)
168 {
169  config base = luaW_checkconfig(L, 1);
170  config merge = luaW_checkconfig(L, 2);
171  const std::string mode = lua_isstring(L, 3) ? luaL_checkstring(L, 3) : "merge";
172  if(mode == "append") {
173  base.merge_attributes(merge);
174  base.append_children(merge);
175  } else {
176  if(mode == "replace") {
177  for(const auto [key, _] : merge.all_children_view()) {
178  base.clear_children(key);
179  }
180  } else if(mode != "merge") {
181  return luaL_argerror(L, 3, "invalid merge mode - must be merge, append, or replace");
182  }
183  base.merge_with(merge);
184  }
185  luaW_pushconfig(L, base);
186  return 1;
187 }
188 
189 /**
190 * Computes a diff of two WML tables
191 * Arg 1: left table
192 * Arg 2: right table
193 */
194 static int intf_wml_diff(lua_State* L)
195 {
196  config lhs = luaW_checkconfig(L, 1);
197  config rhs = luaW_checkconfig(L, 2);
198  luaW_pushconfig(L, lhs.get_diff(rhs));
199  return 1;
200 }
201 
202 /**
203 * Applies a diff to a WML table
204 * Arg 1: base table
205 * Arg 2: WML diff
206 */
207 static int intf_wml_patch(lua_State* L)
208 {
209  config base = luaW_checkconfig(L, 1);
210  config patch = luaW_checkconfig(L, 2);
211  base.apply_diff(patch);
212  luaW_pushconfig(L, base);
213  return 1;
214 }
215 
216 /**
217 * Tests if two WML tables are equal (have the same keys and values, same tags, recursively)
218 * Arg 1: left table
219 * Arg 2: right table
220 */
221 static int intf_wml_equal(lua_State* L)
222 {
223  config left = luaW_checkconfig(L, 1);
224  config right = luaW_checkconfig(L, 2);
225  lua_pushboolean(L, left == right);
226  return 1;
227 }
228 
229 /**
230 * Tests if a table represents a valid WML table
231 * Arg 1: table
232 */
233 static int intf_wml_valid(lua_State* L)
234 {
235  config test;
236  if(luaW_toconfig(L, 1, test)) {
237  // The validate_wml call is PROBABLY redundant, but included just in case validation changes and toconfig isn't updated to match
238  lua_pushboolean(L, test.validate_wml());
239  } else lua_pushboolean(L, false);
240  return 1;
241 }
242 
243 int luaW_open(lua_State* L) {
244  auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
245  lk.add_log("Adding wml module...\n");
246  static luaL_Reg const wml_callbacks[]= {
247  { "load", &intf_load_wml},
248  { "parse", &intf_parse_wml},
249  { "clone", &intf_clone_wml},
250  { "merge", &intf_wml_merge},
251  { "diff", &intf_wml_diff},
252  { "patch", &intf_wml_patch},
253  { "equal", &intf_wml_equal},
254  { "valid", &intf_wml_valid},
255  { "matches_filter", &intf_wml_matches_filter},
256  { "tostring", &intf_wml_tostring},
257  { "interpolate", &intf_wml_interpolate},
258  { nullptr, nullptr },
259  };
260  lua_newtable(L);
261  luaL_setfuncs(L, wml_callbacks, 0);
262  return 1;
263 }
264 
265 }
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
bool matches(const config &filter) const
Definition: config.cpp:1194
bool validate_wml() const
Returns true if this object represents valid WML, i.e.
Definition: config.cpp:1348
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:810
void merge_attributes(const config &)
Definition: config.cpp:742
void clear_children(T... keys)
Definition: config.hpp:616
void merge_with(const config &c)
Merge config 'c' into this config, overwriting this config's values.
Definition: config.cpp:1123
void apply_diff(const config &diff, bool track=false)
A function to apply a diff config onto this config object.
Definition: config.cpp:1026
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:910
void append_children(const config &cfg)
Adds children from cfg.
Definition: config.cpp:167
Realization of serialization/validator.hpp abstract validator.
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:176
std::size_t i
Definition: function.cpp:1029
static std::string _(const char *str)
Definition: gettext.hpp:93
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:837
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:927
void luaW_pushvconfig(lua_State *L, const vconfig &cfg)
Pushes a vconfig on the top of the stack.
Definition: lua_common.cpp:539
bool luaW_toboolean(lua_State *L, int n)
Definition: lua_common.cpp:998
bool luaW_toconfig(lua_State *L, int index, config &cfg)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:849
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
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:207
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:154
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:221
int luaW_open(lua_State *L)
Definition: lua_wml.cpp:243
static int intf_wml_valid(lua_State *L)
Tests if a table represents a valid WML table Arg 1: table.
Definition: lua_wml.cpp:233
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:140
static int intf_parse_wml(lua_State *L)
Parses a WML string into a config; does not preprocess or validate.
Definition: lua_wml.cpp:100
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:194
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:121
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:167
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.
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:629
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:766
static map_location::direction n