The Battle for Wesnoth  1.17.0-dev
lua_stringx.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 
18 #include "scripting/lua_common.hpp"
19 #include "scripting/push_check.hpp"
20 
21 #include "formula/string_utils.hpp"
22 #include "variable.hpp" // for config_variable_set
23 
24 #include <boost/algorithm/string/trim.hpp>
25 
26 #include "lua/lua.h"
27 #include "lua/lauxlib.h"
28 
29 namespace lua_stringx {
30 
31 /**
32 * Formats a message by interpolating WML variable syntax
33 * Arg 1: (optional) Logger
34 * Arg 2: Message
35 */
36 static int intf_format(lua_State* L)
37 {
38  config cfg = luaW_checkconfig(L, 2);
39  config_variable_set variables(cfg);
40  if(lua_isstring(L, 1)) {
41  std::string str = lua_tostring(L, 1);
43  return 1;
44  }
45  t_string str = luaW_checktstring(L, 1);
47  return 1;
48 }
49 
50 /**
51 * Formats a list into human-readable format
52 * Arg 1: default value, used if the list is empty
53 * Arg 2: list of strings
54 */
55 template<bool conjunct>
57 {
58  const t_string empty = luaW_checktstring(L, 1);
59  auto values = lua_check<std::vector<t_string>>(L, 2);
60  lua_push(L, (conjunct ? utils::format_conjunct_list : utils::format_disjunct_list)(empty, values));
61  return 1;
62 }
63 
64 /**
65 * Enables indexing a string by an integer, while also treating the stringx module as its metatable.__index
66 */
67 static int impl_str_index(lua_State* L)
68 {
69  if(lua_type(L, 2) == LUA_TSTRING) {
70  // return stringx[key]
71  lua_getglobal(L, "stringx");
72  lua_pushvalue(L, 2);
73  lua_gettable(L, -2);
74  return 1;
75  } else if(lua_type(L, 2) == LUA_TNUMBER) {
76  // get the string length and the index
77  int len = lua_rawlen(L, 1);
78  int i = luaL_checkinteger(L, 2);
79  // In order to not break ipairs, an out-of-bounds access needs to return nil
80  if(i == 0 || abs(i) > len) {
81  lua_pushnil(L);
82  return 1;
83  }
84  // return string.sub(str, key, key)
85  luaW_getglobal(L, "string", "sub");
86  lua_pushvalue(L, 1);
87  lua_pushvalue(L, 2);
88  lua_pushvalue(L, 2);
89  lua_call(L, 3, 1);
90  return 1;
91  }
92  return 0;
93 }
94 
95 /**
96 * Splits a string into parts according to options
97 * Arg 1: String to split
98 * Arg 2: Separator
99 * Arg 3: Options table
100 */
101 static int intf_str_split(lua_State* L)
102 {
103  enum {BASIC, ESCAPED, PAREN, ANIM} type = BASIC;
104  const std::string& str = luaL_checkstring(L, 1);
105  const std::string& sep = luaL_optstring(L, 2, ",");
106  std::string left, right;
108  if(lua_istable(L, 3)) {
109  flags = 0;
110  if(luaW_table_get_def(L, 3, "remove_empty", true)) {
111  flags |= utils::REMOVE_EMPTY;
112  }
113  if(luaW_table_get_def(L, 3, "strip_spaces", true)) {
114  flags |= utils::STRIP_SPACES;
115  }
116  bool anim = luaW_table_get_def(L, 3, "expand_anim", false);
117  if(luaW_tableget(L, 3, "escape")) {
118  if(anim) {
119  return luaL_error(L, "escape and expand_anim options are incompatible!");
120  }
121  type = ESCAPED;
122  left = luaL_checkstring(L, -1);
123  if(left.size() != 1) {
124  return luaL_error(L, "escape must be a single character");
125  }
126  } else if(luaW_tableget(L, 3, "quote")) {
127  left = right = luaL_checkstring(L, -1);
128  if(anim) {
129  type = ANIM;
130  left.push_back('[');
131  right.push_back(']');
132  } else type = PAREN;
133  } else if(luaW_tableget(L, 3, "quote_left") && luaW_tableget(L, 3, "quote_right")) {
134  left = luaL_checkstring(L, -2);
135  right = luaL_checkstring(L, -1);
136  if(anim) {
137  if(left.find_first_of("[]") != std::string::npos || right.find_first_of("[]") != std::string::npos) {
138  return luaL_error(L, "left and right cannot include square brackets [] if expand_anim is enabled");
139  }
140  type = ANIM;
141  left.push_back('[');
142  right.push_back(']');
143  } else type = PAREN;
144  } else if(anim) {
145  type = ANIM;
146  left = "([";
147  right = ")]";
148  }
149  if(type != ESCAPED && left.size() != right.size()) {
150  return luaL_error(L, "left and right need to be strings of the same length");
151  }
152  }
153  switch(type) {
154  case BASIC:
155  lua_push(L, utils::split(str, sep[0], flags));
156  break;
157  case ESCAPED:
158  lua_push(L, utils::quoted_split(str, sep[0], flags, left[0]));
159  break;
160  case PAREN:
161  lua_push(L, utils::parenthetical_split(str, sep[0], left, right, flags));
162  break;
163  case ANIM:
164  lua_push(L, utils::square_parenthetical_split(str, sep[0], left, right, flags));
165  break;
166  }
167  return 1;
168 }
169 
170 /**
171 * Splits a string into parenthesized portions and portions between parenthesized portions
172 * Arg 1: String to split
173 * Arg 2: Possible left parentheses
174 * Arg 3: Matching right parentheses
175 */
177 {
178  const std::string& str = luaL_checkstring(L, 1);
179  const std::string& left = luaL_optstring(L, 2, "(");
180  const std::string& right = luaL_optstring(L, 3, ")");
181  if(left.size() != right.size()) {
182  return luaL_error(L, "left and right need to be strings of the same length");
183  }
184  bool strip_spaces = luaL_opt(L, luaW_toboolean, 4, true);
185  lua_push(L, utils::parenthetical_split(str, 0, left, right, strip_spaces ? utils::STRIP_SPACES : 0));
186  return 1;
187 }
188 
189 /**
190 * Splits a string into a map
191 * Arg 1: string to split
192 * Arg 2: Separator for items
193 * Arg 3: Separator for key and value
194 */
196 {
197  const std::string& str = luaL_checkstring(L, 1);
198  const std::string& sep = luaL_optstring(L, 2, ",");
199  const std::string& kv = luaL_optstring(L, 3, ":");
200  std::string dflt;
201  if(sep.size() != 1) {
202  return luaL_error(L, "separator must be a single character");
203  }
204  if(kv.size() != 1) {
205  return luaL_error(L, "key_value_separator must be a single character");
206  }
208  if(lua_istable(L, 4)) {
209  flags = 0;
210  if(luaW_table_get_def(L, 4, "remove_empty", true)) {
211  flags |= utils::REMOVE_EMPTY;
212  }
213  if(luaW_table_get_def(L, 4, "strip_spaces", true)) {
214  flags |= utils::STRIP_SPACES;
215  }
216  if(luaW_tableget(L, 4, "default")) {
217  dflt = luaL_checkstring(L, -1);
218  }
219  }
220  lua_push(L, utils::map_split(str, sep[0], kv[0], flags, dflt));
221  return 1;
222 }
223 
224 /**
225 * Joins a list into a string; calls __tostring and __index metamethods
226 * Arg 1: list to join
227 * Arg 2: separator
228 * (arguments can be swapped)
229 */
230 static int intf_str_join(lua_State* L) {
231  // Support both join(list, [sep]) and join(sep, list)
232  // The latter form means sep:join(list) also works.
233  std::string sep;
234  int list_idx;
235  if(lua_istable(L, 1)) {
236  list_idx = 1;
237  sep = luaL_optstring(L, 2, ",");
238  } else if(lua_istable(L, 2)) {
239  sep = luaL_checkstring(L, 1);
240  list_idx = 2;
241  } else return luaL_error(L, "invalid arguments to join, should have map and separator");
242  std::vector<std::string> pieces;
243  for(int i = 1; i <= luaL_len(L, list_idx); i++) {
244  lua_getglobal(L, "tostring");
245  lua_geti(L, list_idx, i);
246  lua_call(L, 1, 1);
247  pieces.push_back(luaL_checkstring(L, -1));
248  }
249  lua_push(L, utils::join(pieces, sep));
250  return 1;
251 }
252 
253 /**
254 * Joins a map into a string; calls __tostring metamethods (on both key and value) but not __index
255 * Arg 1: list to join
256 * Arg 2: separator for items
257 * Arg 3: separator for key and value
258 * (list argument can be swapped to any position)
259 */
260 static int intf_str_join_map(lua_State* L) {
261  // Support join_map(map, [sep], [kv_sep]), join_map(sep, map, [kv_sep]), and join_map(sep, kv_sep, map)
262  // The latter forms mean sep:join_map(kv_sep, map) and sep:join_map(map) also work.
263  // If only one separator is given in the first form, it will be sep, not kv_sep
264  std::string sep, kv;
265  int map_idx;
266  if(lua_istable(L, 1)) {
267  map_idx = 1;
268  sep = luaL_optstring(L, 2, ",");
269  kv = luaL_optstring(L, 3, ":");
270  } else if(lua_istable(L, 2)) {
271  sep = luaL_checkstring(L, 1);
272  map_idx = 2;
273  kv = luaL_optstring(L, 3, ":");
274  } else if(lua_istable(L, 3)) {
275  sep = luaL_checkstring(L, 1);
276  kv = luaL_checkstring(L, 2);
277  map_idx = 3;
278  } else return luaL_error(L, "invalid arguments to join_map, should have map, separator, and key_value_separator");
279  std::map<std::string, std::string> pieces;
280  for(lua_pushnil(L); lua_next(L, map_idx); /*pop in loop body*/) {
281  int key_idx = lua_absindex(L, -2), val_idx = lua_absindex(L, -1);
282  lua_getglobal(L, "tostring");
283  lua_pushvalue(L, key_idx);
284  lua_call(L, 1, 1);
285  std::string& val = pieces[luaL_checkstring(L, -1)];
286  lua_getglobal(L, "tostring");
287  lua_pushvalue(L, val_idx);
288  lua_call(L, 1, 1);
289  val = luaL_checkstring(L, -1);
290  lua_settop(L, key_idx);
291  }
292  lua_push(L, utils::join_map(pieces, sep, kv));
293  return 1;
294 }
295 
296 /**
297  * Trims whitespace from the beginning and end of a string
298  */
299 static int intf_str_trim(lua_State* L)
300 {
301  std::string str = luaL_checkstring(L, 1);
302  boost::trim(str);
303  lua_pushlstring(L, str.c_str(), str.size());
304  return 1;
305 }
306 
307 // Override string.format to coerce the format to a string
309 {
310  int nargs = lua_gettop(L);
311  if(luaW_iststring(L, 1)) {
312  // get the tostring() function and call it on the first argument
313  lua_getglobal(L, "tostring");
314  lua_pushvalue(L, 1);
315  lua_call(L, 1, 1);
316  // replace the first argument with the coerced value
317  lua_replace(L, 1);
318  }
319  // grab the original string.format function from the closure...
321  // ...move it to the bottom of the stack...
322  lua_insert(L, 1);
323  // ...and finally pass along all the arguments to it.
324  lua_call(L, nargs, 1);
325  return 1;
326 }
327 
328 /**
329  * Parses a range string of the form a-b into an interval pair
330  * Accepts the string "infinity" as representing a Very Large Number
331  */
333 {
334  const std::string str = luaL_checkstring(L, 1);
335  auto interval = utils::parse_range(str);
336  lua_pushnumber(L, interval.first);
337  lua_pushnumber(L, interval.second);
338  return 2;
339 }
340 
342  auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
343  lk.add_log("Adding stringx module...\n");
344  static luaL_Reg const str_callbacks[] = {
345  { "split", &intf_str_split },
346  { "parenthetical_split", &intf_str_paren_split },
347  { "map_split", &intf_str_map_split },
348  { "join", &intf_str_join },
349  { "join_map", &intf_str_join_map },
350  { "trim", &intf_str_trim },
351  { "parse_range", &intf_parse_range },
352  { "vformat", &intf_format },
353  { "format_conjunct_list", &intf_format_list<true> },
354  { "format_disjunct_list", &intf_format_list<false> },
355  { nullptr, nullptr },
356  };
357  lua_newtable(L);
358  luaL_setfuncs(L, str_callbacks, 0);
359  // Set the stringx metatable to index the string module
360  lua_createtable(L, 0, 1);
361  lua_getglobal(L, "string");
362  lua_setfield(L, -2, "__index");
363  lua_setmetatable(L, -2);
364 
365  // Set the metatable of strings to index the stringx module instead of the string module
366  lua_pushliteral(L, "");
367  lua_getmetatable(L, -1);
369  lua_setfield(L, -2, "__index");
370  lua_setmetatable(L, -2);
371  lua_pop(L, 1);
372 
373  // Override string.format so it can accept a t_string
374  lua_getglobal(L, "string");
375  lua_getfield(L, -1, "format");
377  lua_setfield(L, -2, "format");
378  lua_pop(L, 1);
379  return 1;
380 }
381 
382 }
bool luaW_tableget(lua_State *L, int index, const char *key)
LUA_API void lua_createtable(lua_State *L, int narray, int nrec)
Definition: lapi.cpp:728
#define lua_pushcfunction(L, f)
Definition: lua.h:370
std::decay_t< T > luaW_table_get_def(lua_State *L, int index, std::string_view k, const T &def)
returns t[k] where k is the table at index index and k is k or def if it is not convertible to the co...
Definition: push_check.hpp:395
std::string format_conjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a conjunctive list.
std::pair< int, int > parse_range(const std::string &str)
LUALIB_API lua_Integer luaL_len(lua_State *L, int idx)
Definition: lauxlib.cpp:849
std::string join_map(const T &v, const std::string &major=",", const std::string &minor=":")
std::string interpolate_variables_into_string(const std::string &str, const string_map *const symbols)
Function which will interpolate variables, starting with &#39;$&#39; in the string &#39;str&#39; with the equivalent ...
static int intf_str_paren_split(lua_State *L)
Splits a string into parenthesized portions and portions between parenthesized portions Arg 1: String...
LUA_API void lua_settop(lua_State *L, int idx)
Definition: lapi.cpp:173
LUA_API int lua_type(lua_State *L, int idx)
Definition: lapi.cpp:260
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
LUA_API int lua_gettop(lua_State *L)
Definition: lapi.cpp:168
LUA_API int lua_gettable(lua_State *L, int idx)
Definition: lapi.cpp:640
LUA_API int lua_getglobal(lua_State *L, const char *name)
Definition: lapi.cpp:632
std::string format_disjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a disjunctive list.
LUA_API void lua_pushcclosure(lua_State *L, lua_CFunction fn, int n)
Definition: lapi.cpp:555
static int intf_str_join_map(lua_State *L)
Joins a map into a string; calls __tostring metamethods (on both key and value) but not __index Arg 1...
std::map< std::string, std::string > map_split(const std::string &val, char major, char minor, int flags, const std::string &default_value)
Splits a string based on two separators into a map.
void lua_push(lua_State *L, const T &val)
Definition: push_check.hpp:385
LUA_API int lua_absindex(lua_State *L, int idx)
Definition: lapi.cpp:161
static int intf_str_format(lua_State *L)
#define lua_pop(L, n)
Definition: lua.h:364
#define lua_upvalueindex(i)
Definition: lua.h:45
#define LUA_TSTRING
Definition: lua.h:69
bool luaW_getglobal(lua_State *L, const std::vector< std::string > &path)
Pushes the value found by following the variadic names (char *), if the value is not nil...
Definition: lua_common.cpp:953
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
int luaW_open(lua_State *L)
#define luaL_opt(L, f, n, d)
Definition: lauxlib.h:151
t_string luaW_checktstring(lua_State *L, int index)
Converts a scalar to a translatable string.
Definition: lua_common.cpp:627
LUA_API const char * lua_pushlstring(lua_State *L, const char *s, size_t len)
Definition: lapi.cpp:502
#define LUA_TNUMBER
Definition: lua.h:68
LUA_API int lua_getmetatable(lua_State *L, int objindex)
Definition: lapi.cpp:741
LUA_API int lua_setmetatable(lua_State *L, int objindex)
Definition: lapi.cpp:901
#define lua_newtable(L)
Definition: lua.h:366
LUA_API void lua_pushnil(lua_State *L)
Definition: lapi.cpp:473
LUA_API void lua_pushnumber(lua_State *L, lua_Number n)
Definition: lapi.cpp:481
#define lua_pushliteral(L, s)
Definition: lua.h:381
lu_byte right
Definition: lparser.cpp:1227
static int intf_str_map_split(lua_State *L)
Splits a string into a map Arg 1: string to split Arg 2: Separator for items Arg 3: Separator for key...
static int intf_parse_range(lua_State *L)
Parses a range string of the form a-b into an interval pair Accepts the string "infinity" as represen...
std::size_t i
Definition: function.cpp:967
LUALIB_API lua_Integer luaL_checkinteger(lua_State *L, int arg)
Definition: lauxlib.cpp:442
bool luaW_iststring(lua_State *L, int index)
Definition: lua_common.cpp:635
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:901
std::vector< std::string > quoted_split(const std::string &val, char c, int flags, char quote)
This function is identical to split(), except it does not split when it otherwise would if the previo...
static int impl_str_index(lua_State *L)
Enables indexing a string by an integer, while also treating the stringx module as its metatable...
Definition: lua_stringx.cpp:67
#define lua_replace(L, idx)
Definition: lua.h:393
#define lua_tostring(L, i)
Definition: lua.h:386
LUA_API void lua_pushvalue(lua_State *L, int idx)
Definition: lapi.cpp:246
#define lua_call(L, n, r)
Definition: lua.h:283
LUALIB_API int luaL_error(lua_State *L, const char *fmt,...)
Definition: lauxlib.cpp:234
REMOVE_EMPTY: remove empty elements.
LUA_API lua_Unsigned lua_rawlen(lua_State *L, int idx)
Definition: lapi.cpp:402
#define lua_istable(L, n)
Definition: lua.h:373
std::vector< std::string > split(const config_attribute_value &val)
static int intf_str_split(lua_State *L)
Splits a string into parts according to options Arg 1: String to split Arg 2: Separator Arg 3: Option...
lu_byte left
Definition: lparser.cpp:1226
static int intf_format_list(lua_State *L)
Formats a list into human-readable format Arg 1: default value, used if the list is empty Arg 2: list...
Definition: lua_stringx.cpp:56
#define lua_insert(L, idx)
Definition: lua.h:389
void trim(std::string_view &s)
LUA_API int lua_geti(lua_State *L, int idx, lua_Integer n)
Definition: lapi.cpp:661
static int intf_str_join(lua_State *L)
Joins a list into a string; calls __tostring and __index metamethods Arg 1: list to join Arg 2: separ...
LUALIB_API void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup)
Definition: lauxlib.cpp:904
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
LUA_API int lua_getfield(lua_State *L, int idx, const char *k)
Definition: lapi.cpp:655
static int intf_format(lua_State *L)
Formats a message by interpolating WML variable syntax Arg 1: (optional) Logger Arg 2: Message...
Definition: lua_stringx.cpp:36
t_string interpolate_variables_into_tstring(const t_string &tstr, const variable_set &variables)
Function that does the same as the above, for t_stringS.
#define luaL_optstring(L, n, d)
Definition: lauxlib.h:139
LUA_API void lua_setfield(lua_State *L, int idx, const char *k)
Definition: lapi.cpp:837
LUA_API int lua_next(lua_State *L, int idx)
Definition: lapi.cpp:1220
std::vector< std::string > square_parenthetical_split(const std::string &val, const char separator, const std::string &left, const std::string &right, const int flags)
Similar to parenthetical_split, but also expands embedded square brackets.
static int intf_str_trim(lua_State *L)
Trims whitespace from the beginning and end of a string.
std::vector< std::string > parenthetical_split(const std::string &val, const char separator, const std::string &left, const std::string &right, const int flags)
Splits a string based either on a separator, except then the text appears within specified parenthesi...
#define luaL_checkstring(L, n)
Definition: lauxlib.h:138