You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

139 lines
4.5KB

  1. local doc = require "wslua.doc"
  2. local fndef = require "wslua.fndef"
  3. local ty = require "wslua.ty"
  4. local list = require "wslua.list"
  5. local string = setmetatable({}, {__index = string})
  6. doc.module{
  7. name = "wslua.string",
  8. what = "Additional string functionality",
  9. descr = [[
  10. This module also contains all functions from the default `string` library, and can be used as a drop-in replacement. Use
  11. the main `wslua` module to inject this module into the string metatable.
  12. ]],
  13. }
  14. string.split = fndef({
  15. name = "string.split",
  16. what = "Split a string into segments using a separator.",
  17. args = {
  18. {name = "subj", type = ty.stringish, what = "String to be split."},
  19. {name = "sep", type = ty.string & ~ ty.eq(""), what = "Separator pattern to split on."},
  20. {name = "plain", type = #ty.boolean, what = "Match `sep` literally, not as a pattern."},
  21. },
  22. ret = {
  23. {type = ty.class(list) & ty.list(ty.string, 1)},
  24. },
  25. tests = {
  26. function() return "{}", string.split("", "/"):__tostring() end,
  27. function() return "{1, 2, 3}", string.split("1/2/3", "/", true):__tostring() end,
  28. function() return "{1, 2, 3}", string.split("1 2 3", "%s"):__tostring() end,
  29. function() return "{1, 2, }", string.split("1 2 ", "%s"):__tostring() end,
  30. function() return "{, 2, 3}", string.split(" 2 3", "%s"):__tostring() end,
  31. function() return "{1, 2, , 3}", string.split("1 2 3", "%s"):__tostring() end,
  32. },
  33. },
  34. function(subj, sep)
  35. subj = tostring(subj)
  36. local ret = list()
  37. local i = 1
  38. while true do
  39. local a, b = subj:find(sep, i, plain)
  40. if not a then break end
  41. ret:insert(subj:sub(i, a-1))
  42. i = b + 1
  43. end
  44. ret:insert(subj:sub(i, -1))
  45. return ret
  46. end)
  47. string.print = fndef({
  48. name = "string.print",
  49. what = "Print a string to a stream.",
  50. descr = [[
  51. Mainly useful if injected into the string metatable using the main `wslua` module, that allows `:print()` at the end of
  52. a long call chain.
  53. ]],
  54. args = {
  55. {name = "s", type = ty.string},
  56. {name = "file", type = #ty.file, what = "File to print to, defaults to `stdout`."},
  57. {name = "nonl", type = #ty.boolean, what = "Don't add a newline after the string, defaults to `false` (= add a newline)."},
  58. },
  59. ret = {
  60. {type = ty.string, what = "s"},
  61. }
  62. },
  63. function(s, file, nonl)
  64. file = file or io.stdout
  65. file:write(s)
  66. if not nonl then file:write("\n") end
  67. return s
  68. end)
  69. string.template = fndef({
  70. name = "string.template",
  71. what = "Process a string with embedded Lua snippets.",
  72. descr = [=[
  73. There are two kinds of snippets: expression snippets and statement snippets. Expression snippets start with `%[[` and
  74. end with the first `]]` afterwards, statement snippets are delimited by `%{{` and `}}`. An arbitrary amount of `=`s can
  75. be placed between the brackets, with semantics analogous to Lua strings/comments.
  76. The function "inverts" the string:
  77. - Everything except the snippets is put inside string literals, which are fed to a concatenation function.
  78. - Expression snippets are wrapped in `tostring` calls, and then also fed to the concatenation function.
  79. - Statement snippets are left as-is between the other code.
  80. The resulting code is executed inside the specified `env`.
  81. ]=],
  82. args = {
  83. {name = "input", type = ty.stringish, what = "String to process."},
  84. {name = "loc", type = ty.stringish, what = "Location information used to generate error messages."},
  85. {name = "env", type = ty.table, what = "Environment to execute snippets in."},
  86. },
  87. ret = {
  88. {type = ty.string},
  89. },
  90. tests = {
  91. function() return "", string.template("", "test", {}) end,
  92. function() return "asdfg", string.template("as%[=[d]=]fg", "test", {d = "d"}) end,
  93. function() return "Hello, World!", string.template("Hello, %[[name()]]!", "test", {name = function() return "World" end}) end,
  94. function() return "Hello, World!", string.template("%{{ name = 'World' }}Hello, %[[ name ]]!", "test", {}) end,
  95. },
  96. ex = [=[
  97. require "wslua" ();
  98. local t = "%{{ name = 'World' }}%[[greeting]], %[[name]]!"
  99. s:template("test string", {name = "Lua"}):print()
  100. ]=]
  101. },
  102. function(input, loc, env)
  103. input = tostring(input )
  104. loc = tostring(loc )
  105. local s = table.concat({
  106. "local ___out___ = ...; ___out___[=========[",
  107. input
  108. :gsub("%%%[(=*)%[(.-)%]%1%]", "]=========] ___out___(%2) ___out___[=========[")
  109. :gsub("%%{(=*){(.-)}%1}", "]=========] %2 ___out___[=========[")
  110. :gsub("___out___%[=========%[\n", "___out___'\\n' %1"),
  111. "]=========]"
  112. })
  113. local ret = list{}
  114. assert(load(s, tostring(loc), "t", env))(function(s) ret:insert(tostring(s)) end)
  115. return ret:concat()
  116. end)
  117. return string