The Battle for Wesnoth  1.17.4+dev
lua_wml.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2022
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 
22 #include "serialization/parser.hpp"
24 #include "variable.hpp" // for config_variable_set
25 
26 #include <fstream>
27 
28 #include "lua/lauxlib.h"
29 
30 namespace lua_wml {
31 
32 /**
33 * Dumps a wml table or userdata wml object into a pretty string.
34 * - Arg 1: wml table or vconfig userdata
35 * - Ret 1: string
36 */
37 static int intf_wml_tostring(lua_State* L) {
38  const config& arg = luaW_checkconfig(L, 1);
39  std::ostringstream stream;
40  write(stream, arg);
41  lua_pushstring(L, stream.str().c_str());
42  return 1;
43 }
44 
45 /**
46  * Loads a WML file into a config
47  * - Arg 1: WML file path
48  * - Arg 2: (optional) Array of preprocessor defines, or false to skip preprocessing (true is also valid)
49  * - Arg 3: (optional) Path to a schema file for validation (omit for no validation)
50  * - Ret: config
51  */
52 static int intf_load_wml(lua_State* L)
53 {
54  std::string file = luaL_checkstring(L, 1);
55  bool preprocess = true;
56  preproc_map defines_map;
57  if(lua_type(L, 2) == LUA_TBOOLEAN) {
58  preprocess = luaW_toboolean(L, 2);
59  } else if(lua_type(L, 2) == LUA_TTABLE || lua_type(L, 2) == LUA_TUSERDATA) {
60  lua_len(L, 2);
61  int n = lua_tointeger(L, -1);
62  lua_pop(L, 1);
63  for(int i = 0; i < n; i++) {
64  lua_geti(L, 2, i);
65  if(!lua_isstring(L, -1)) {
66  return luaL_argerror(L, 2, "expected bool or array of strings");
67  }
68  std::string define = lua_tostring(L, -1);
69  lua_pop(L, 1);
70  if(!define.empty()) {
71  defines_map.emplace(define, preproc_define(define));
72  }
73  }
74  } else if(!lua_isnoneornil(L, 2)) {
75  return luaL_argerror(L, 2, "expected bool or array of strings");
76  }
77  std::string schema_path = luaL_optstring(L, 3, "");
78  std::shared_ptr<schema_validation::schema_validator> validator;
79  if(!schema_path.empty()) {
80  validator.reset(new schema_validation::schema_validator(filesystem::get_wml_location(schema_path)));
81  validator->set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
82  }
83  std::string wml_file = filesystem::get_wml_location(file);
85  config result;
86  if(preprocess) {
87  stream = preprocess_file(wml_file, &defines_map);
88  } else {
89  stream.reset(new std::ifstream(wml_file));
90  }
91  read(result, *stream, validator.get());
92  luaW_pushconfig(L, result);
93  return 1;
94 }
95 
96 /**
97  * Parses a WML string into a config; does not preprocess or validate
98  * - Arg 1: WML string
99  * - Ret: config
100  */
101 static int intf_parse_wml(lua_State* L)
102 {
103  std::string wml = luaL_checkstring(L, 1);
104  std::string schema_path = luaL_optstring(L, 2, "");
105  std::shared_ptr<schema_validation::schema_validator> validator;
106  if(!schema_path.empty()) {
107  validator.reset(new schema_validation::schema_validator(filesystem::get_wml_location(schema_path)));
108  validator->set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
109  }
110  config result;
111  read(result, wml, validator.get());
112  luaW_pushconfig(L, result);
113  return 1;
114 }
115 
116 /**
117  * Returns a clone (deep copy) of the passed config, which can be either a normal config or a vconfig
118  * If it is a vconfig, the underlying config is also cloned.
119  * - Arg 1: a config
120  * - Ret: the cloned config
121  */
122 static int intf_clone_wml(lua_State* L)
123 {
124  const vconfig* vcfg = nullptr;
125  const config& cfg = luaW_checkconfig(L, 1, vcfg);
126  if(vcfg) {
127  config clone_underlying = vcfg->get_config();
128  vconfig clone(clone_underlying);
129  luaW_pushvconfig(L, clone);
130  } else {
131  luaW_pushconfig(L, cfg);
132  }
133  return 1;
134 }
135 
136 /**
137 * Interpolates variables into a WML table, including [insert_tag]
138 * Arg 1: WML table to interpolate into
139 * Arg 2: WML table of variables
140 */
141 static int intf_wml_interpolate(lua_State* L)
142 {
143  config cfg = luaW_checkconfig(L, 1), vars_cfg = luaW_checkconfig(L, 2);
144  config_variable_set vars(vars_cfg);
145  vconfig vcfg(cfg, vars);
147  return 1;
148 }
149 
150 /**
151 * Tests if a WML table matches a filter
152 * Arg 1: table to test
153 * Arg 2: filter
154 */
155 static int intf_wml_matches_filter(lua_State* L)
156 {
157  config cfg = luaW_checkconfig(L, 1);
158  config filter = luaW_checkconfig(L, 2);
159  lua_pushboolean(L, cfg.matches(filter));
160  return 1;
161 }
162 
163 /**
164 * Merges two WML tables
165 * Arg 1: base table
166 * Arg 2: table to merge in
167 */
168 static int intf_wml_merge(lua_State* L)
169 {
170  config base = luaW_checkconfig(L, 1);
171  config merge = luaW_checkconfig(L, 2);
172  const std::string mode = lua_isstring(L, 3) ? luaL_checkstring(L, 3) : "merge";
173  if(mode == "append") {
174  base.merge_attributes(merge);
175  base.append_children(merge);
176  } else {
177  if(mode == "replace") {
178  for(const auto c : merge.all_children_range()) {
179  base.clear_children(c.key);
180  }
181  } else if(mode != "merge") {
182  return luaL_argerror(L, 3, "invalid merge mode - must be merge, append, or replace");
183  }
184  base.merge_with(merge);
185  }
186  luaW_pushconfig(L, base);
187  return 1;
188 }
189 
190 /**
191 * Computes a diff of two WML tables
192 * Arg 1: left table
193 * Arg 2: right table
194 */
195 static int intf_wml_diff(lua_State* L)
196 {
197  config lhs = luaW_checkconfig(L, 1);
198  config rhs = luaW_checkconfig(L, 2);
199  luaW_pushconfig(L, lhs.get_diff(rhs));
200  return 1;
201 }
202 
203 /**
204 * Applies a diff to a WML table
205 * Arg 1: base table
206 * Arg 2: WML diff
207 */
208 static int intf_wml_patch(lua_State* L)
209 {
210  config base = luaW_checkconfig(L, 1);
211  config patch = luaW_checkconfig(L, 2);
212  base.apply_diff(patch);
213  luaW_pushconfig(L, base);
214  return 1;
215 }
216 
217 /**
218 * Tests if two WML tables are equal (have the same keys and values, same tags, recursively)
219 * Arg 1: left table
220 * Arg 2: right table
221 */
222 static int intf_wml_equal(lua_State* L)
223 {
224  config left = luaW_checkconfig(L, 1);
225  config right = luaW_checkconfig(L, 2);
226  lua_pushboolean(L, left == right);
227  return 1;
228 }
229 
230 /**
231 * Tests if a table represents a valid WML table
232 * Arg 1: table
233 */
234 static int intf_wml_valid(lua_State* L)
235 {
236  config test;
237  if(luaW_toconfig(L, 1, test)) {
238  // The validate_wml call is PROBABLY redundant, but included just in case validation changes and toconfig isn't updated to match
239  lua_pushboolean(L, test.validate_wml());
240  } else lua_pushboolean(L, false);
241  return 1;
242 }
243 
244 int luaW_open(lua_State* L) {
245  auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
246  lk.add_log("Adding wml module...\n");
247  static luaL_Reg const wml_callbacks[]= {
248  { "load", &intf_load_wml},
249  { "parse", &intf_parse_wml},
250  { "clone", &intf_clone_wml},
251  { "merge", &intf_wml_merge},
252  { "diff", &intf_wml_diff},
253  { "patch", &intf_wml_patch},
254  { "equal", &intf_wml_equal},
255  { "valid", &intf_wml_valid},
256  { "matches_filter", &intf_wml_matches_filter},
257  { "tostring", &intf_wml_tostring},
258  { "interpolate", &intf_wml_interpolate},
259  { nullptr, nullptr },
260  };
261  lua_newtable(L);
262  luaL_setfuncs(L, wml_callbacks, 0);
263  return 1;
264 }
265 
266 }
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:222
bool matches(const config &filter) const
Definition: config.cpp:1299
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:978
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:811
void clear_children(T... keys)
Definition: config.hpp:556
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:141
const attribute_value * get(config_key_type key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:780
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:168
static int intf_parse_wml(lua_State *L)
Parses a WML string into a config; does not preprocess or validate.
Definition: lua_wml.cpp:101
void append_children(const config &cfg)
Adds children from cfg.
Definition: config.cpp:223
config get_diff(const config &c) const
A function to get the differences between this object, and &#39;c&#39;, as another config object...
Definition: config.cpp:1004
void merge_with(const config &c)
Merge config &#39;c&#39; into this config, overwriting this config&#39;s values.
Definition: config.cpp:1224
One of the realizations of serialization/validator.hpp abstract validator.
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:155
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:764
bool luaW_toboolean(lua_State *L, int n)
Definition: lua_common.cpp:971
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:627
bool validate_wml() const
Returns true if this object represents valid WML, i.e.
Definition: config.cpp:1461
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:39
static int intf_load_wml(lua_State *L)
Loads a WML file into a config.
Definition: lua_wml.cpp:52
config get_parsed_config() const
Definition: variable.cpp:176
void apply_diff(const config &diff, bool track=false)
A function to apply a diff config onto this config object.
Definition: config.cpp:1125
int luaW_open(lua_State *L)
Definition: lua_wml.cpp:244
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:122
bool luaW_toconfig(lua_State *L, int index, config &cfg)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:823
std::size_t i
Definition: function.cpp:967
std::string get_wml_location(const std::string &filename, const std::string &current_dir)
Returns a complete path to the actual WML file or directory or an empty string if the file isn&#39;t pres...
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:900
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:208
void merge_attributes(const config &)
Definition: config.cpp:837
void luaW_pushvconfig(lua_State *L, const vconfig &cfg)
Pushes a vconfig on the top of the stack.
Definition: lua_common.cpp:530
A variable-expanding proxy for the config class.
Definition: variable.hpp:44
const config & get_config() const
Definition: variable.hpp:75
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:195
std::map< std::string, struct preproc_define > preproc_map
static int intf_wml_valid(lua_State *L)
Tests if a table represents a valid WML table Arg 1: table.
Definition: lua_wml.cpp:234
Realization of serialization/validator.hpp abstract validator.
static int intf_wml_tostring(lua_State *L)
Dumps a wml table or userdata wml object into a pretty string.
Definition: lua_wml.cpp:37
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map *defines)
Function to use the WML preprocessor on a file.
mock_char c
static map_location::DIRECTION n