The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
19
23#include "variable.hpp" // for config_variable_set
24
25#include <fstream>
26
27#include "lua/wrapper_lauxlib.h"
28
29namespace 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*/
36static 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 */
51static 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 */
98static 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 */
118static 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 {
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*/
137static 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*/
151static int intf_wml_matches_filter(lua_State* L)
152{
154 config filter = luaW_checkconfig(L, 2);
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*/
164static 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*/
191static 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*/
204static 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*/
218static 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*/
230static 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
240int 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.
A variable-expanding proxy for the config class.
Definition variable.hpp:45
config get_parsed_config() const
Definition variable.cpp:177
const config & get_config() const
Definition variable.hpp:75
const config * cfg
static std::string _(const char *str)
Definition gettext.hpp:100
void luaW_pushconfig(lua_State *L, const config &cfg)
Converts a config object to a Lua table pushed at the top of the stack.
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
void luaW_pushvconfig(lua_State *L, const vconfig &cfg)
Pushes a vconfig on the top of the stack.
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.
utils::optional< std::string > get_wml_location(const std::string &path, const utils::optional< std::string > &current_dir=utils::nullopt)
Returns a translated path to the actual file or directory, if it exists.
std::unique_ptr< std::istream > scoped_istream
config read(std::istream &in, abstract_validator *validator)
Definition parser.cpp:610
void write(std::ostream &out, const configr_of &cfg, unsigned int level, bool strong_quotes)
Definition parser.cpp:748
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
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.
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
static map_location::direction n
#define i