git.m455.casa

fa

clone url: git://git.m455.casa/fa


src/lume.lua

1 --
2 -- lume
3 --
4 -- Copyright (c) 2020 rxi
5 --
6 -- Permission is hereby granted, free of charge, to any person obtaining a copy of
7 -- this software and associated documentation files (the "Software"), to deal in
8 -- the Software without restriction, including without limitation the rights to
9 -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10 -- of the Software, and to permit persons to whom the Software is furnished to do
11 -- so, subject to the following conditions:
12 --
13 -- The above copyright notice and this permission notice shall be included in all
14 -- copies or substantial portions of the Software.
15 --
16 -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 -- SOFTWARE.
23 --
24
25 local lume = { _version = "2.3.0" }
26
27 local pairs, ipairs = pairs, ipairs
28 local type, assert, unpack = type, assert, unpack or table.unpack
29 local tostring, tonumber = tostring, tonumber
30 local math_floor = math.floor
31 local math_ceil = math.ceil
32 local math_atan2 = math.atan2 or math.atan
33 local math_sqrt = math.sqrt
34 local math_abs = math.abs
35
36 local noop = function()
37 end
38
39 local identity = function(x)
40 return x
41 end
42
43 local patternescape = function(str)
44 return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1")
45 end
46
47 local absindex = function(len, i)
48 return i < 0 and (len + i + 1) or i
49 end
50
51 local iscallable = function(x)
52 if type(x) == "function" then return true end
53 local mt = getmetatable(x)
54 return mt and mt.__call ~= nil
55 end
56
57 local getiter = function(x)
58 if lume.isarray(x) then
59 return ipairs
60 elseif type(x) == "table" then
61 return pairs
62 end
63 error("expected table", 3)
64 end
65
66 local iteratee = function(x)
67 if x == nil then return identity end
68 if iscallable(x) then return x end
69 if type(x) == "table" then
70 return function(z)
71 for k, v in pairs(x) do
72 if z[k] ~= v then return false end
73 end
74 return true
75 end
76 end
77 return function(z) return z[x] end
78 end
79
80
81
82 function lume.clamp(x, min, max)
83 return x < min and min or (x > max and max or x)
84 end
85
86
87 function lume.round(x, increment)
88 if increment then return lume.round(x / increment) * increment end
89 return x >= 0 and math_floor(x + .5) or math_ceil(x - .5)
90 end
91
92
93 function lume.sign(x)
94 return x < 0 and -1 or 1
95 end
96
97
98 function lume.lerp(a, b, amount)
99 return a + (b - a) * lume.clamp(amount, 0, 1)
100 end
101
102
103 function lume.smooth(a, b, amount)
104 local t = lume.clamp(amount, 0, 1)
105 local m = t * t * (3 - 2 * t)
106 return a + (b - a) * m
107 end
108
109
110 function lume.pingpong(x)
111 return 1 - math_abs(1 - x % 2)
112 end
113
114
115 function lume.distance(x1, y1, x2, y2, squared)
116 local dx = x1 - x2
117 local dy = y1 - y2
118 local s = dx * dx + dy * dy
119 return squared and s or math_sqrt(s)
120 end
121
122
123 function lume.angle(x1, y1, x2, y2)
124 return math_atan2(y2 - y1, x2 - x1)
125 end
126
127
128 function lume.vector(angle, magnitude)
129 return math.cos(angle) * magnitude, math.sin(angle) * magnitude
130 end
131
132
133 function lume.random(a, b)
134 if not a then a, b = 0, 1 end
135 if not b then b = 0 end
136 return a + math.random() * (b - a)
137 end
138
139
140 function lume.randomchoice(t)
141 return t[math.random(#t)]
142 end
143
144
145 function lume.weightedchoice(t)
146 local sum = 0
147 for _, v in pairs(t) do
148 assert(v >= 0, "weight value less than zero")
149 sum = sum + v
150 end
151 assert(sum ~= 0, "all weights are zero")
152 local rnd = lume.random(sum)
153 for k, v in pairs(t) do
154 if rnd < v then return k end
155 rnd = rnd - v
156 end
157 end
158
159
160 function lume.isarray(x)
161 return type(x) == "table" and x[1] ~= nil
162 end
163
164
165 function lume.push(t, ...)
166 local n = select("#", ...)
167 for i = 1, n do
168 t[#t + 1] = select(i, ...)
169 end
170 return ...
171 end
172
173
174 function lume.remove(t, x)
175 local iter = getiter(t)
176 for i, v in iter(t) do
177 if v == x then
178 if lume.isarray(t) then
179 table.remove(t, i)
180 break
181 else
182 t[i] = nil
183 break
184 end
185 end
186 end
187 return x
188 end
189
190
191 function lume.clear(t)
192 local iter = getiter(t)
193 for k in iter(t) do
194 t[k] = nil
195 end
196 return t
197 end
198
199
200 function lume.extend(t, ...)
201 for i = 1, select("#", ...) do
202 local x = select(i, ...)
203 if x then
204 for k, v in pairs(x) do
205 t[k] = v
206 end
207 end
208 end
209 return t
210 end
211
212
213 function lume.shuffle(t)
214 local rtn = {}
215 for i = 1, #t do
216 local r = math.random(i)
217 if r ~= i then
218 rtn[i] = rtn[r]
219 end
220 rtn[r] = t[i]
221 end
222 return rtn
223 end
224
225
226 function lume.sort(t, comp)
227 local rtn = lume.clone(t)
228 if comp then
229 if type(comp) == "string" then
230 table.sort(rtn, function(a, b) return a[comp] < b[comp] end)
231 else
232 table.sort(rtn, comp)
233 end
234 else
235 table.sort(rtn)
236 end
237 return rtn
238 end
239
240
241 function lume.array(...)
242 local t = {}
243 for x in ... do t[#t + 1] = x end
244 return t
245 end
246
247
248 function lume.each(t, fn, ...)
249 local iter = getiter(t)
250 if type(fn) == "string" then
251 for _, v in iter(t) do v[fn](v, ...) end
252 else
253 for _, v in iter(t) do fn(v, ...) end
254 end
255 return t
256 end
257
258
259 function lume.map(t, fn)
260 fn = iteratee(fn)
261 local iter = getiter(t)
262 local rtn = {}
263 for k, v in iter(t) do rtn[k] = fn(v) end
264 return rtn
265 end
266
267
268 function lume.all(t, fn)
269 fn = iteratee(fn)
270 local iter = getiter(t)
271 for _, v in iter(t) do
272 if not fn(v) then return false end
273 end
274 return true
275 end
276
277
278 function lume.any(t, fn)
279 fn = iteratee(fn)
280 local iter = getiter(t)
281 for _, v in iter(t) do
282 if fn(v) then return true end
283 end
284 return false
285 end
286
287
288 function lume.reduce(t, fn, first)
289 local started = first ~= nil
290 local acc = first
291 local iter = getiter(t)
292 for _, v in iter(t) do
293 if started then
294 acc = fn(acc, v)
295 else
296 acc = v
297 started = true
298 end
299 end
300 assert(started, "reduce of an empty table with no first value")
301 return acc
302 end
303
304
305 function lume.unique(t)
306 local rtn = {}
307 for k in pairs(lume.invert(t)) do
308 rtn[#rtn + 1] = k
309 end
310 return rtn
311 end
312
313
314 function lume.filter(t, fn, retainkeys)
315 fn = iteratee(fn)
316 local iter = getiter(t)
317 local rtn = {}
318 if retainkeys then
319 for k, v in iter(t) do
320 if fn(v) then rtn[k] = v end
321 end
322 else
323 for _, v in iter(t) do
324 if fn(v) then rtn[#rtn + 1] = v end
325 end
326 end
327 return rtn
328 end
329
330
331 function lume.reject(t, fn, retainkeys)
332 fn = iteratee(fn)
333 local iter = getiter(t)
334 local rtn = {}
335 if retainkeys then
336 for k, v in iter(t) do
337 if not fn(v) then rtn[k] = v end
338 end
339 else
340 for _, v in iter(t) do
341 if not fn(v) then rtn[#rtn + 1] = v end
342 end
343 end
344 return rtn
345 end
346
347
348 function lume.merge(...)
349 local rtn = {}
350 for i = 1, select("#", ...) do
351 local t = select(i, ...)
352 local iter = getiter(t)
353 for k, v in iter(t) do
354 rtn[k] = v
355 end
356 end
357 return rtn
358 end
359
360
361 function lume.concat(...)
362 local rtn = {}
363 for i = 1, select("#", ...) do
364 local t = select(i, ...)
365 if t ~= nil then
366 local iter = getiter(t)
367 for _, v in iter(t) do
368 rtn[#rtn + 1] = v
369 end
370 end
371 end
372 return rtn
373 end
374
375
376 function lume.find(t, value)
377 local iter = getiter(t)
378 for k, v in iter(t) do
379 if v == value then return k end
380 end
381 return nil
382 end
383
384
385 function lume.match(t, fn)
386 fn = iteratee(fn)
387 local iter = getiter(t)
388 for k, v in iter(t) do
389 if fn(v) then return v, k end
390 end
391 return nil
392 end
393
394
395 function lume.count(t, fn)
396 local count = 0
397 local iter = getiter(t)
398 if fn then
399 fn = iteratee(fn)
400 for _, v in iter(t) do
401 if fn(v) then count = count + 1 end
402 end
403 else
404 if lume.isarray(t) then
405 return #t
406 end
407 for _ in iter(t) do count = count + 1 end
408 end
409 return count
410 end
411
412
413 function lume.slice(t, i, j)
414 i = i and absindex(#t, i) or 1
415 j = j and absindex(#t, j) or #t
416 local rtn = {}
417 for x = i < 1 and 1 or i, j > #t and #t or j do
418 rtn[#rtn + 1] = t[x]
419 end
420 return rtn
421 end
422
423
424 function lume.first(t, n)
425 if not n then return t[1] end
426 return lume.slice(t, 1, n)
427 end
428
429
430 function lume.last(t, n)
431 if not n then return t[#t] end
432 return lume.slice(t, -n, -1)
433 end
434
435
436 function lume.invert(t)
437 local rtn = {}
438 for k, v in pairs(t) do rtn[v] = k end
439 return rtn
440 end
441
442
443 function lume.pick(t, ...)
444 local rtn = {}
445 for i = 1, select("#", ...) do
446 local k = select(i, ...)
447 rtn[k] = t[k]
448 end
449 return rtn
450 end
451
452
453 function lume.keys(t)
454 local rtn = {}
455 local iter = getiter(t)
456 for k in iter(t) do rtn[#rtn + 1] = k end
457 return rtn
458 end
459
460
461 function lume.clone(t)
462 local rtn = {}
463 for k, v in pairs(t) do rtn[k] = v end
464 return rtn
465 end
466
467
468 function lume.fn(fn, ...)
469 assert(iscallable(fn), "expected a function as the first argument")
470 local args = { ... }
471 return function(...)
472 local a = lume.concat(args, { ... })
473 return fn(unpack(a))
474 end
475 end
476
477
478 function lume.once(fn, ...)
479 local f = lume.fn(fn, ...)
480 local done = false
481 return function(...)
482 if done then return end
483 done = true
484 return f(...)
485 end
486 end
487
488
489 local memoize_fnkey = {}
490 local memoize_nil = {}
491
492 function lume.memoize(fn)
493 local cache = {}
494 return function(...)
495 local c = cache
496 for i = 1, select("#", ...) do
497 local a = select(i, ...) or memoize_nil
498 c[a] = c[a] or {}
499 c = c[a]
500 end
501 c[memoize_fnkey] = c[memoize_fnkey] or {fn(...)}
502 return unpack(c[memoize_fnkey])
503 end
504 end
505
506
507 function lume.combine(...)
508 local n = select('#', ...)
509 if n == 0 then return noop end
510 if n == 1 then
511 local fn = select(1, ...)
512 if not fn then return noop end
513 assert(iscallable(fn), "expected a function or nil")
514 return fn
515 end
516 local funcs = {}
517 for i = 1, n do
518 local fn = select(i, ...)
519 if fn ~= nil then
520 assert(iscallable(fn), "expected a function or nil")
521 funcs[#funcs + 1] = fn
522 end
523 end
524 return function(...)
525 for _, f in ipairs(funcs) do f(...) end
526 end
527 end
528
529
530 function lume.call(fn, ...)
531 if fn then
532 return fn(...)
533 end
534 end
535
536
537 function lume.time(fn, ...)
538 local start = os.clock()
539 local rtn = {fn(...)}
540 return (os.clock() - start), unpack(rtn)
541 end
542
543
544 local lambda_cache = {}
545
546 function lume.lambda(str)
547 if not lambda_cache[str] then
548 local args, body = str:match([[^([%w,_ ]-)%->(.-)$]])
549 assert(args and body, "bad string lambda")
550 local s = "return function(" .. args .. ")\nreturn " .. body .. "\nend"
551 lambda_cache[str] = lume.dostring(s)
552 end
553 return lambda_cache[str]
554 end
555
556
557 local serialize
558
559 local serialize_map = {
560 [ "boolean" ] = tostring,
561 [ "nil" ] = tostring,
562 [ "string" ] = function(v) return string.format("%q", v) end,
563 [ "number" ] = function(v)
564 if v ~= v then return "0/0" -- nan
565 elseif v == 1 / 0 then return "1/0" -- inf
566 elseif v == -1 / 0 then return "-1/0" end -- -inf
567 return tostring(v)
568 end,
569 [ "table" ] = function(t, stk)
570 stk = stk or {}
571 if stk[t] then error("circular reference") end
572 local rtn = {}
573 stk[t] = true
574 for k, v in pairs(t) do
575 rtn[#rtn + 1] = "[" .. serialize(k, stk) .. "]=" .. serialize(v, stk)
576 end
577 stk[t] = nil
578 return "{" .. table.concat(rtn, ",") .. "}"
579 end
580 }
581
582 setmetatable(serialize_map, {
583 __index = function(_, k) error("unsupported serialize type: " .. k) end
584 })
585
586 serialize = function(x, stk)
587 return serialize_map[type(x)](x, stk)
588 end
589
590 function lume.serialize(x)
591 return serialize(x)
592 end
593
594
595 function lume.deserialize(str)
596 return lume.dostring("return " .. str)
597 end
598
599
600 function lume.split(str, sep)
601 if not sep then
602 return lume.array(str:gmatch("([%S]+)"))
603 else
604 assert(sep ~= "", "empty separator")
605 local psep = patternescape(sep)
606 return lume.array((str..sep):gmatch("(.-)("..psep..")"))
607 end
608 end
609
610
611 function lume.trim(str, chars)
612 if not chars then return str:match("^[%s]*(.-)[%s]*$") end
613 chars = patternescape(chars)
614 return str:match("^[" .. chars .. "]*(.-)[" .. chars .. "]*$")
615 end
616
617
618 function lume.wordwrap(str, limit)
619 limit = limit or 72
620 local check
621 if type(limit) == "number" then
622 check = function(s) return #s >= limit end
623 else
624 check = limit
625 end
626 local rtn = {}
627 local line = ""
628 for word, spaces in str:gmatch("(%S+)(%s*)") do
629 local s = line .. word
630 if check(s) then
631 table.insert(rtn, line .. "\n")
632 line = word
633 else
634 line = s
635 end
636 for c in spaces:gmatch(".") do
637 if c == "\n" then
638 table.insert(rtn, line .. "\n")
639 line = ""
640 else
641 line = line .. c
642 end
643 end
644 end
645 table.insert(rtn, line)
646 return table.concat(rtn)
647 end
648
649
650 function lume.format(str, vars)
651 if not vars then return str end
652 local f = function(x)
653 return tostring(vars[x] or vars[tonumber(x)] or "{" .. x .. "}")
654 end
655 return (str:gsub("{(.-)}", f))
656 end
657
658
659 function lume.trace(...)
660 local info = debug.getinfo(2, "Sl")
661 local t = { info.short_src .. ":" .. info.currentline .. ":" }
662 for i = 1, select("#", ...) do
663 local x = select(i, ...)
664 if type(x) == "number" then
665 x = string.format("%g", lume.round(x, .01))
666 end
667 t[#t + 1] = tostring(x)
668 end
669 print(table.concat(t, " "))
670 end
671
672
673 function lume.dostring(str)
674 return assert((loadstring or load)(str))()
675 end
676
677
678 function lume.uuid()
679 local fn = function(x)
680 local r = math.random(16) - 1
681 r = (x == "x") and (r + 1) or (r % 4) + 9
682 return ("0123456789abcdef"):sub(r, r)
683 end
684 return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn))
685 end
686
687
688 function lume.hotswap(modname)
689 local oldglobal = lume.clone(_G)
690 local updated = {}
691 local function update(old, new)
692 if updated[old] then return end
693 updated[old] = true
694 local oldmt, newmt = getmetatable(old), getmetatable(new)
695 if oldmt and newmt then update(oldmt, newmt) end
696 for k, v in pairs(new) do
697 if type(v) == "table" then update(old[k], v) else old[k] = v end
698 end
699 end
700 local err = nil
701 local function onerror(e)
702 for k in pairs(_G) do _G[k] = oldglobal[k] end
703 err = lume.trim(e)
704 end
705 local ok, oldmod = pcall(require, modname)
706 oldmod = ok and oldmod or nil
707 xpcall(function()
708 package.loaded[modname] = nil
709 local newmod = require(modname)
710 if type(oldmod) == "table" then update(oldmod, newmod) end
711 for k, v in pairs(oldglobal) do
712 if v ~= _G[k] and type(v) == "table" then
713 update(v, _G[k])
714 _G[k] = v
715 end
716 end
717 end, onerror)
718 package.loaded[modname] = oldmod
719 if err then return nil, err end
720 return oldmod
721 end
722
723
724 local ripairs_iter = function(t, i)
725 i = i - 1
726 local v = t[i]
727 if v ~= nil then
728 return i, v
729 end
730 end
731
732 function lume.ripairs(t)
733 return ripairs_iter, t, (#t + 1)
734 end
735
736
737 function lume.color(str, mul)
738 mul = mul or 1
739 local r, g, b, a
740 r, g, b = str:match("#(%x%x)(%x%x)(%x%x)")
741 if r then
742 r = tonumber(r, 16) / 0xff
743 g = tonumber(g, 16) / 0xff
744 b = tonumber(b, 16) / 0xff
745 a = 1
746 elseif str:match("rgba?%s*%([%d%s%.,]+%)") then
747 local f = str:gmatch("[%d.]+")
748 r = (f() or 0) / 0xff
749 g = (f() or 0) / 0xff
750 b = (f() or 0) / 0xff
751 a = f() or 1
752 else
753 error(("bad color string '%s'"):format(str))
754 end
755 return r * mul, g * mul, b * mul, a * mul
756 end
757
758
759 local chain_mt = {}
760 chain_mt.__index = lume.map(lume.filter(lume, iscallable, true),
761 function(fn)
762 return function(self, ...)
763 self._value = fn(self._value, ...)
764 return self
765 end
766 end)
767 chain_mt.__index.result = function(x) return x._value end
768
769 function lume.chain(value)
770 return setmetatable({ _value = value }, chain_mt)
771 end
772
773 setmetatable(lume, {
774 __call = function(_, ...)
775 return lume.chain(...)
776 end
777 })
778
779
780 return lume