The Battle for Wesnoth  1.15.5+dev
lua_stringx.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2018 by Chris Beck <render787@gmail.com>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
17 #include "scripting/lua_common.hpp"
18 #include "scripting/push_check.hpp"
19 
20 #include "formula/string_utils.hpp"
21 #include "variable.hpp" // for config_variable_set
22 
23 #include <boost/algorithm/string/trim.hpp>
24 
25 #include "lua/lua.h"
26 #include "lua/lauxlib.h"
27 
28 namespace lua_stringx {
29 
30 /**
31 * Formats a message by interpolating WML variable syntax
32 * Arg 1: (optional) Logger
33 * Arg 2: Message
34 */
35 static int intf_format(lua_State* L)
36 {
37  config cfg = luaW_checkconfig(L, 2);
38  config_variable_set variables(cfg);
39  if(lua_isstring(L, 1)) {
42  return 1;
43  }
46  return 1;
47 }
48 
49 /**
50 * Formats a list into human-readable format
51 * Arg 1: default value, used if the list is empty
52 * Arg 2: list of strings
53 */
54 template<bool conjunct>
56 {
57  const t_string empty = luaW_checktstring(L, 1);
58  auto values = lua_check<std::vector<t_string>>(L, 2);
59  lua_push(L, (conjunct ? utils::format_conjunct_list : utils::format_disjunct_list)(empty, values));
60  return 1;
61 }
62 
63 /**
64 * Enables indexing a string by an integer, while also treating the stringx module as its metatable.__index
65 */
66 static int impl_str_index(lua_State* L)
67 {
68  if(lua_type(L, 2) == LUA_TSTRING) {
69  // return stringx[key]
70  lua_getglobal(L, "stringx");
71  lua_pushvalue(L, 2);
72  lua_gettable(L, -2);
73  return 1;
74  } else if(lua_type(L, 2) == LUA_TNUMBER) {
75  // return string.sub(str, key, key)
76  luaW_getglobal(L, "string", "sub");
77  lua_pushvalue(L, 1);
78  lua_pushvalue(L, 2);
79  lua_pushvalue(L, 2);
80  lua_call(L, 3, 1);
81  return 1;
82  }
83  return 0;
84 }
85 
86 /**
87 * Splits a string into parts according to options
88 * Arg 1: String to split
89 * Arg 2: Separator
90 * Arg 3: Options table
91 */
92 static int intf_str_split(lua_State* L)
93 {
94  enum {BASIC, ESCAPED, PAREN, ANIM} type = BASIC;
95  const std::string& str = luaL_checkstring(L, 1);
96  const std::string& sep = luaL_optstring(L, 2, ",");
99  if(lua_istable(L, 3)) {
100  flags = 0;
101  if(luaW_table_get_def(L, 3, "remove_empty", true)) {
102  flags |= utils::REMOVE_EMPTY;
103  }
104  if(luaW_table_get_def(L, 3, "strip_spaces", true)) {
105  flags |= utils::STRIP_SPACES;
106  }
107  bool anim = luaW_table_get_def(L, 3, "expand_anim", false);
108  if(luaW_tableget(L, 3, "escape")) {
109  if(anim) {
110  return luaL_error(L, "escape and expand_anim options are incompatible!");
111  }
112  type = ESCAPED;
113  left = luaL_checkstring(L, -1);
114  if(left.size() != 1) {
115  return luaL_error(L, "escape must be a single character");
116  }
117  } else if(luaW_tableget(L, 3, "quote")) {
118  left = right = luaL_checkstring(L, -1);
119  if(anim) {
120  type = ANIM;
121  left.push_back('[');
122  right.push_back(']');
123  } else type = PAREN;
124  } else if(luaW_tableget(L, 3, "quote_left") && luaW_tableget(L, 3, "quote_right")) {
125  left = luaL_checkstring(L, -2);
126  right = luaL_checkstring(L, -1);
127  if(anim) {
128  if(left.find_first_of("[]") != std::string::npos || right.find_first_of("[]") != std::string::npos) {
129  return luaL_error(L, "left and right cannot include square brackets [] if expand_anim is enabled");
130  }
131  type = ANIM;
132  left.push_back('[');
133  right.push_back(']');
134  } else type = PAREN;
135  } else if(anim) {
136  type = ANIM;
137  left = "([";
138  right = ")]";
139  }
140  if(type != ESCAPED && left.size() != right.size()) {
141  return luaL_error(L, "left and right need to be strings of the same length");
142  }
143  }
144  switch(type) {
145  case BASIC:
146  lua_push(L, utils::split(str, sep[0], flags));
147  break;
148  case ESCAPED:
149  lua_push(L, utils::quoted_split(str, sep[0], flags, left[0]));
150  break;
151  case PAREN:
152  lua_push(L, utils::parenthetical_split(str, sep[0], left, right, flags));
153  break;
154  case ANIM:
155  lua_push(L, utils::square_parenthetical_split(str, sep[0], left, right, flags));
156  break;
157  }
158  return 1;
159 }
160 
161 /**
162 * Splits a string into parenthesized portions and portions between parenthesized portions
163 * Arg 1: String to split
164 * Arg 2: Possible left parentheses
165 * Arg 3: Matching right parentheses
166 */
168 {
169  const std::string& str = luaL_checkstring(L, 1);
170  const std::string& left = luaL_optstring(L, 2, "(");
171  const std::string& right = luaL_optstring(L, 3, ")");
172  if(left.size() != right.size()) {
173  return luaL_error(L, "left and right need to be strings of the same length");
174  }
175  bool strip_spaces = luaL_opt(L, luaW_toboolean, 4, true);
176  lua_push(L, utils::parenthetical_split(str, 0, left, right, strip_spaces ? utils::STRIP_SPACES : 0));
177  return 1;
178 }
179 
180 /**
181 * Splits a string into a map
182 * Arg 1: string to split
183 * Arg 2: Separator for items
184 * Arg 3: Separator for key and value
185 */
187 {
188  const std::string& str = luaL_checkstring(L, 1);
189  const std::string& sep = luaL_optstring(L, 2, ",");
190  const std::string& kv = luaL_optstring(L, 3, ":");
191  std::string dflt;
192  if(sep.size() != 1) {
193  return luaL_error(L, "separator must be a single character");
194  }
195  if(kv.size() != 1) {
196  return luaL_error(L, "key_value_separator must be a single character");
197  }
199  if(lua_istable(L, 4)) {
200  flags = 0;
201  if(luaW_table_get_def(L, 4, "remove_empty", true)) {
202  flags |= utils::REMOVE_EMPTY;
203  }
204  if(luaW_table_get_def(L, 4, "strip_spaces", true)) {
205  flags |= utils::STRIP_SPACES;
206  }
207  if(luaW_tableget(L, 4, "default")) {
208  dflt = luaL_checkstring(L, -1);
209  }
210  }
211  lua_push(L, utils::map_split(str, sep[0], kv[0], flags, dflt));
212  return 1;
213 }
214 
215 /**
216 * Joins a list into a string; calls __tostring and __index metamethods
217 * Arg 1: list to join
218 * Arg 2: separator
219 * (arguments can be swapped)
220 */
221 static int intf_str_join(lua_State* L) {
222  // Support both join(list, [sep]) and join(sep, list)
223  // The latter form means sep:join(list) also works.
224  std::string sep;
225  int list_idx;
226  if(lua_istable(L, 1)) {
227  list_idx = 1;
228  sep = luaL_optstring(L, 2, ",");
229  } else if(lua_istable(L, 2)) {
230  sep = luaL_checkstring(L, 1);
231  list_idx = 2;
232  } else return luaL_error(L, "invalid arguments to join, should have map and separator");
233  std::vector<std::string> pieces;
234  for(int i = 1; i <= luaL_len(L, list_idx); i++) {
235  lua_getglobal(L, "tostring");
236  lua_geti(L, list_idx, i);
237  lua_call(L, 1, 1);
238  pieces.push_back(luaL_checkstring(L, -1));
239  }
240  lua_push(L, utils::join(pieces, sep));
241  return 1;
242 }
243 
244 /**
245 * Joins a map into a string; calls __tostring metamethods (on both key and value) but not __index
246 * Arg 1: list to join
247 * Arg 2: separator for items
248 * Arg 3: separator for key and value
249 * (list argument can be swapped to any position)
250 */
251 static int intf_str_join_map(lua_State* L) {
252  // Support join_map(map, [sep], [kv_sep]), join_map(sep, map, [kv_sep]), and join_map(sep, kv_sep, map)
253  // The latter forms mean sep:join_map(kv_sep, map) and sep:join_map(map) also work.
254  // If only one separator is given in the first form, it will be sep, not kv_sep
255  std::string sep, kv;
256  int map_idx;
257  if(lua_istable(L, 1)) {
258  map_idx = 1;
259  sep = luaL_optstring(L, 2, ",");
260  kv = luaL_optstring(L, 3, ":");
261  } else if(lua_istable(L, 2)) {
262  sep = luaL_checkstring(L, 1);
263  map_idx = 2;
264  kv = luaL_optstring(L, 3, ":");
265  } else if(lua_istable(L, 3)) {
266  sep = luaL_checkstring(L, 1);
267  kv = luaL_checkstring(L, 2);
268  map_idx = 3;
269  } else return luaL_error(L, "invalid arguments to join_map, should have map, separator, and key_value_separator");
270  std::map<std::string, std::string> pieces;
271  for(lua_pushnil(L); lua_next(L, map_idx); /*pop in loop body*/) {
272  int key_idx = lua_absindex(L, -2), val_idx = lua_absindex(L, -1);
273  lua_getglobal(L, "tostring");
274  lua_pushvalue(L, key_idx);
275  lua_call(L, 1, 1);
276  std::string& val = pieces[luaL_checkstring(L, -1)];
277  lua_getglobal(L, "tostring");
278  lua_pushvalue(L, val_idx);
279  lua_call(L, 1, 1);
280  val = luaL_checkstring(L, -1);
281  lua_settop(L, key_idx);
282  }
283  lua_push(L, utils::join_map(pieces, sep, kv));
284  return 1;
285 }
286 
287 /**
288  * Trims whitespace from the beginning and end of a string
289  */
290 static int intf_str_trim(lua_State* L)
291 {
293  boost::trim(str);
294  lua_pushlstring(L, str.c_str(), str.size());
295  return 1;
296 }
297 
298 // Override string.format to coerce the format to a string
300 {
301  int nargs = lua_gettop(L);
302  if(luaW_iststring(L, 1)) {
303  // get the tostring() function and call it on the first argument
304  lua_getglobal(L, "tostring");
305  lua_pushvalue(L, 1);
306  lua_call(L, 1, 1);
307  // replace the first argument with the coerced value
308  lua_replace(L, 1);
309  }
310  // grab the original string.format function from the closure...
312  // ...move it to the bottom of the stack...
313  lua_insert(L, 1);
314  // ...and finally pass along all the arguments to it.
315  lua_call(L, nargs, 1);
316  return 1;
317 }
318 
319 /**
320  * Parses a range string of the form a-b into an interval pair
321  * Accepts the string "infinity" as representing a Very Large Number
322  */
324 {
325  const std::string str = luaL_checkstring(L, 1);
326  auto interval = utils::parse_range(str);
327  lua_pushnumber(L, interval.first);
328  lua_pushnumber(L, interval.second);
329  return 2;
330 }
331 
333  auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
334  lk.add_log("Adding stringx module...\n");
335  static luaL_Reg const str_callbacks[] = {
336  { "split", &intf_str_split },
337  { "parenthetical_split", &intf_str_paren_split },
338  { "map_split", &intf_str_map_split },
339  { "join", &intf_str_join },
340  { "join_map", &intf_str_join_map },
341  { "trim", &intf_str_trim },
342  { "parse_range", &intf_parse_range },
343  { "vformat", &intf_format },
344  { "format_conjunct_list", &intf_format_list<true> },
345  { "format_disjunct_list", &intf_format_list<false> },
346  { nullptr, nullptr },
347  };
348  lua_newtable(L);
349  luaL_setfuncs(L, str_callbacks, 0);
350  // Set the stringx metatable to index the string module
351  lua_createtable(L, 0, 1);
352  lua_getglobal(L, "string");
353  lua_setfield(L, -2, "__index");
354  lua_setmetatable(L, -2);
355 
356  // Set the metatable of strings to index the stringx module instead of the string module
357  lua_pushliteral(L, "");
358  lua_getmetatable(L, -1);
360  lua_setfield(L, -2, "__index");
361  lua_setmetatable(L, -2);
362  lua_pop(L, 1);
363 
364  // Override string.format so it can accept a t_string
365  lua_getglobal(L, "string");
366  lua_getfield(L, -1, "format");
368  lua_setfield(L, -2, "format");
369  lua_pop(L, 1);
370  return 1;
371 }
372 
373 }
bool luaW_tableget(lua_State *L, int index, const char *key)
Definition: lua_common.cpp:966
LUA_API void lua_createtable(lua_State *L, int narray, int nrec)
Definition: lapi.cpp:684
#define lua_pushcfunction(L, f)
Definition: lua.h:350
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:798
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:172
LUA_API int lua_type(lua_State *L, int idx)
Definition: lapi.cpp:251
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:167
REMOVE_EMPTY: remove empty elements.
LUA_API int lua_gettable(lua_State *L, int idx)
Definition: lapi.cpp:612
LUA_API int lua_getglobal(lua_State *L, const char *name)
Definition: lapi.cpp:605
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:532
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.
std::string str
Definition: statement.cpp:110
void lua_push(lua_State *L, const T &val)
Definition: push_check.hpp:400
LUA_API int lua_absindex(lua_State *L, int idx)
Definition: lapi.cpp:160
static int intf_str_format(lua_State *L)
#define lua_pop(L, n)
Definition: lua.h:344
#define lua_upvalueindex(i)
Definition: lua.h:43
#define LUA_TSTRING
Definition: lua.h:68
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:871
LUA_API int lua_isstring(lua_State *L, int idx)
Definition: lapi.cpp:283
bool luaW_toboolean(lua_State *L, int n)
Definition: lua_common.cpp:890
int luaW_open(lua_State *L)
void trim(string_view &s)
#define luaL_opt(L, f, n, d)
Definition: lauxlib.h:137
t_string luaW_checktstring(lua_State *L, int index)
Converts a scalar to a translatable string.
Definition: lua_common.cpp:620
LUA_API const char * lua_pushlstring(lua_State *L, const char *s, size_t len)
Definition: lapi.cpp:479
#define LUA_TNUMBER
Definition: lua.h:67
LUA_API int lua_getmetatable(lua_State *L, int objindex)
Definition: lapi.cpp:697
LUA_API int lua_setmetatable(lua_State *L, int objindex)
Definition: lapi.cpp:846
#define lua_newtable(L)
Definition: lua.h:346
LUA_API void lua_pushnil(lua_State *L)
Definition: lapi.cpp:450
LUA_API void lua_pushnumber(lua_State *L, lua_Number n)
Definition: lapi.cpp:458
#define lua_pushliteral(L, s)
Definition: lua.h:361
lu_byte right
Definition: lparser.cpp:1027
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:933
bool luaW_iststring(lua_State *L, int index)
Definition: lua_common.cpp:628
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:819
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:66
#define lua_replace(L, idx)
Definition: lua.h:373
#define lua_tostring(L, i)
Definition: lua.h:366
lua_check_impl::remove_constref< T > luaW_table_get_def(lua_State *L, int index, utils::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:410
LUA_API void lua_pushvalue(lua_State *L, int idx)
Definition: lapi.cpp:237
#define lua_call(L, n, r)
Definition: lua.h:274
LUALIB_API int luaL_error(lua_State *L, const char *fmt,...)
Definition: lauxlib.cpp:223
#define lua_istable(L, n)
Definition: lua.h:353
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...
Definition: lua_stringx.cpp:92
lu_byte left
Definition: lparser.cpp:1026
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:55
#define lua_insert(L, idx)
Definition: lua.h:369
LUA_API int lua_geti(lua_State *L, int idx, lua_Integer n)
Definition: lapi.cpp:628
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:934
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
LUA_API int lua_getfield(lua_State *L, int idx, const char *k)
Definition: lapi.cpp:622
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:35
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:125
LUA_API void lua_setfield(lua_State *L, int idx, const char *k)
Definition: lapi.cpp:777
LUA_API int lua_next(lua_State *L, int idx)
Definition: lapi.cpp:1123
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:124