std.lua 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. --[[ Stateless utilities missing in lua standard library ]]
  2. ---@param number number
  3. function round(number) return math.floor(number + 0.5) end
  4. ---@param min number
  5. ---@param value number
  6. ---@param max number
  7. function clamp(min, value, max) return math.max(min, math.min(value, max)) end
  8. ---@param rgba string `rrggbb` or `rrggbbaa` hex string.
  9. function serialize_rgba(rgba)
  10. local a = rgba:sub(7, 8)
  11. return {
  12. color = rgba:sub(5, 6) .. rgba:sub(3, 4) .. rgba:sub(1, 2),
  13. opacity = clamp(0, tonumber(#a == 2 and a or 'ff', 16) / 255, 1),
  14. }
  15. end
  16. -- Trim any `char` from the end of the string.
  17. ---@param str string
  18. ---@param char string
  19. ---@return string
  20. function trim_end(str, char)
  21. local char, end_i = char:byte(), 0
  22. for i = #str, 1, -1 do
  23. if str:byte(i) ~= char then
  24. end_i = i
  25. break
  26. end
  27. end
  28. return str:sub(1, end_i)
  29. end
  30. ---@param str string
  31. ---@param pattern string
  32. ---@return string[]
  33. function split(str, pattern)
  34. local list = {}
  35. local full_pattern = '(.-)' .. pattern
  36. local last_end = 1
  37. local start_index, end_index, capture = str:find(full_pattern, 1)
  38. while start_index do
  39. list[#list + 1] = capture
  40. last_end = end_index + 1
  41. start_index, end_index, capture = str:find(full_pattern, last_end)
  42. end
  43. if last_end <= (#str + 1) then
  44. capture = str:sub(last_end)
  45. list[#list + 1] = capture
  46. end
  47. return list
  48. end
  49. -- Handles common option and message inputs that need to be split by comma when strings.
  50. ---@param input string|string[]|nil
  51. ---@return string[]
  52. function comma_split(input)
  53. if not input then return {} end
  54. if type(input) == 'table' then return itable_map(input, tostring) end
  55. local str = tostring(input)
  56. return str:match('^%s*$') and {} or split(str, ' *, *')
  57. end
  58. -- Get index of the last appearance of `sub` in `str`.
  59. ---@param str string
  60. ---@param sub string
  61. ---@return integer|nil
  62. function string_last_index_of(str, sub)
  63. local sub_length = #sub
  64. for i = #str, 1, -1 do
  65. for j = 1, sub_length do
  66. if str:byte(i + j - 1) ~= sub:byte(j) then break end
  67. if j == sub_length then return i end
  68. end
  69. end
  70. end
  71. ---@param itable table
  72. ---@param value any
  73. ---@return integer|nil
  74. function itable_index_of(itable, value)
  75. for index, item in ipairs(itable) do
  76. if item == value then return index end
  77. end
  78. end
  79. ---@param itable table
  80. ---@param value any
  81. ---@return boolean
  82. function itable_has(itable, value)
  83. return itable_index_of(itable, value) ~= nil
  84. end
  85. ---@param itable table
  86. ---@param compare fun(value: any, index: number): boolean|integer|string|nil
  87. ---@param from? number Where to start search, defaults to `1`.
  88. ---@param to? number Where to end search, defaults to `#itable`.
  89. ---@return number|nil index
  90. ---@return any|nil value
  91. function itable_find(itable, compare, from, to)
  92. from, to = from or 1, to or #itable
  93. for index = from, to, from < to and 1 or -1 do
  94. if index > 0 and index <= #itable and compare(itable[index], index) then
  95. return index, itable[index]
  96. end
  97. end
  98. end
  99. ---@param itable table
  100. ---@param decider fun(value: any, index: number): boolean|integer|string|nil
  101. function itable_filter(itable, decider)
  102. local filtered = {}
  103. for index, value in ipairs(itable) do
  104. if decider(value, index) then filtered[#filtered + 1] = value end
  105. end
  106. return filtered
  107. end
  108. ---@param itable table
  109. ---@param value any
  110. function itable_delete_value(itable, value)
  111. for index = 1, #itable, 1 do
  112. if itable[index] == value then table.remove(itable, index) end
  113. end
  114. return itable
  115. end
  116. ---@param itable table
  117. ---@param transformer fun(value: any, index: number) : any
  118. function itable_map(itable, transformer)
  119. local result = {}
  120. for index, value in ipairs(itable) do
  121. result[index] = transformer(value, index)
  122. end
  123. return result
  124. end
  125. ---@param itable table
  126. ---@param start_pos? integer
  127. ---@param end_pos? integer
  128. function itable_slice(itable, start_pos, end_pos)
  129. start_pos = start_pos and start_pos or 1
  130. end_pos = end_pos and end_pos or #itable
  131. if end_pos < 0 then end_pos = #itable + end_pos + 1 end
  132. if start_pos < 0 then start_pos = #itable + start_pos + 1 end
  133. local new_table = {}
  134. for index, value in ipairs(itable) do
  135. if index >= start_pos and index <= end_pos then
  136. new_table[#new_table + 1] = value
  137. end
  138. end
  139. return new_table
  140. end
  141. ---@generic T
  142. ---@param ...T[]|nil
  143. ---@return T[]
  144. function itable_join(...)
  145. local args, result = {...}, {}
  146. for i = 1, select('#', ...) do
  147. if args[i] then for _, value in ipairs(args[i]) do result[#result + 1] = value end end
  148. end
  149. return result
  150. end
  151. ---@param target any[]
  152. ---@param source any[]
  153. function itable_append(target, source)
  154. for _, value in ipairs(source) do target[#target + 1] = value end
  155. return target
  156. end
  157. function itable_clear(itable)
  158. for i = #itable, 1, -1 do itable[i] = nil end
  159. end
  160. ---@generic T
  161. ---@param input table<T, any>
  162. ---@return T[]
  163. function table_keys(input)
  164. local keys = {}
  165. for key, _ in pairs(input) do keys[#keys + 1] = key end
  166. return keys
  167. end
  168. ---@generic T
  169. ---@param input table<any, T>
  170. ---@return T[]
  171. function table_values(input)
  172. local values = {}
  173. for _, value in pairs(input) do values[#values + 1] = value end
  174. return values
  175. end
  176. ---@generic T: table<any, any>
  177. ---@param target T
  178. ---@param ... T|nil
  179. ---@return T
  180. function table_assign(target, ...)
  181. local args = {...}
  182. for i = 1, select('#', ...) do
  183. if type(args[i]) == 'table' then for key, value in pairs(args[i]) do target[key] = value end end
  184. end
  185. return target
  186. end
  187. ---@generic T: table<any, any>
  188. ---@param target T
  189. ---@param source T
  190. ---@param props string[]
  191. ---@return T
  192. function table_assign_props(target, source, props)
  193. for _, name in ipairs(props) do target[name] = source[name] end
  194. return target
  195. end
  196. -- `table_assign({}, input)` without loosing types :(
  197. ---@generic T: table<any, any>
  198. ---@param input T
  199. ---@return T
  200. function table_copy(input) return table_assign({}, input) end
  201. -- Converts itable values into `table<value, true>` map.
  202. ---@param values any[]
  203. function create_set(values)
  204. local result = {}
  205. for _, value in ipairs(values) do result[value] = true end
  206. return result
  207. end
  208. ---@generic T: any
  209. ---@param input string
  210. ---@param value_sanitizer? fun(value: string, key: string): T
  211. ---@return table<string, T>
  212. function serialize_key_value_list(input, value_sanitizer)
  213. local result, sanitize = {}, value_sanitizer or function(value) return value end
  214. for _, key_value_pair in ipairs(comma_split(input)) do
  215. local key, value = key_value_pair:match('^([%w_]+)=([%w%.]+)$')
  216. if key and value then result[key] = sanitize(value, key) end
  217. end
  218. return result
  219. end
  220. --[[ EASING FUNCTIONS ]]
  221. function ease_out_quart(x) return 1 - ((1 - x) ^ 4) end
  222. function ease_out_sext(x) return 1 - ((1 - x) ^ 6) end
  223. --[[ CLASSES ]]
  224. ---@class Class
  225. Class = {}
  226. function Class:new(...)
  227. local object = setmetatable({}, {__index = self})
  228. object:init(...)
  229. return object
  230. end
  231. function Class:init(...) end
  232. function Class:destroy() end
  233. function class(parent) return setmetatable({}, {__index = parent or Class}) end
  234. ---@class CircularBuffer<T> : Class
  235. CircularBuffer = class()
  236. function CircularBuffer:new(max_size) return Class.new(self, max_size) --[[@as CircularBuffer]] end
  237. function CircularBuffer:init(max_size)
  238. self.max_size = max_size
  239. self.pos = 0
  240. self.data = {}
  241. end
  242. function CircularBuffer:insert(item)
  243. self.pos = self.pos % self.max_size + 1
  244. self.data[self.pos] = item
  245. end
  246. function CircularBuffer:get(i)
  247. return i <= #self.data and self.data[(self.pos + i - 1) % #self.data + 1] or nil
  248. end
  249. local function iter(self, i)
  250. if i == #self.data then return nil end
  251. i = i + 1
  252. return i, self:get(i)
  253. end
  254. function CircularBuffer:iter()
  255. return iter, self, 0
  256. end
  257. local function iter_rev(self, i)
  258. if i == 1 then return nil end
  259. i = i - 1
  260. return i, self:get(i)
  261. end
  262. function CircularBuffer:iter_rev()
  263. return iter_rev, self, #self.data + 1
  264. end
  265. function CircularBuffer:head()
  266. return self.data[self.pos]
  267. end
  268. function CircularBuffer:tail()
  269. if #self.data < 1 then return nil end
  270. return self.data[self.pos % #self.data + 1]
  271. end
  272. function CircularBuffer:clear()
  273. itable_clear(self.data)
  274. self.pos = 0
  275. end