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