Roland/src/lib/funky.lua

513 lines
10 KiB
Lua

---@author Emik
---@copyright (c) 2026 Emik
---@license MPL-2.0
---@version 1.0.0
---
---@class F
local f = {}
if not f then
---@generic K, V
---@param self F | { [K]: V }
---@return K?, V?
function f:next()
error()
end
end
---@type F
local none
---@generic K, V
---@param tbl table<K, V>
---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)
---@return fun(tbl: table<K, V>, key: K): K, V
---@return K
---@return V
local function autopairs(tbl, fpairs)
return (fpairs or tbl[#tbl] and ipairs or pairs)(tbl)
end
---@param any any
---@return boolean
local function is_f(any)
return type(any) == "table" and any.from == f.from and any.new == f.new
end
--- Always returns nil.
---@return nil
function f.noop()
end
--- Always returns false.
---@return false
function f.fals()
return false
end
--- Always returns true.
---@return true
function f.tru()
return true
end
--- Returns the arguments.
---@generic T
---@param ... T
---@return T
function f.id(...)
return ...
end
---@generic T
---@param v T
---@return fun(): T
---@nodiscard
function f.const(v)
if v == true then
return f.tru
elseif v == false then
return f.fals
elseif v == nil then
return f.noop
end
return function()
return v
end
end
---@generic K, V
---@param v K
---@return fun(x: { [K]: V }): V
---@nodiscard
function f.index(v)
return function(x)
return x[v]
end
end
---@generic K, V
---@param fnext? fun(): K?, V?
---@return F|{ [K]: V }
---@nodiscard
function f.new(fnext)
local ret = {next = fnext or f.noop}
for k, v in pairs(f) do
ret[k] = v
end
return ret
end
--- Creates an enumeration.
---@generic K, V
---@param tbl table<K, V>
---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K?, V?)
---@param step? nil
---@return F | { [K]: V }
---@overload fun(tbl: number, fpairs?: number, step?: number): F | { [number]: number }
---@overload fun(tbl: string): (fun(table: { [string]: V }): V)
---@overload fun(tbl: false): fun(): false
---@overload fun(tbl: true): fun(): true
---@overload fun(tbl: nil): F
function f.from(tbl, fpairs, step)
if tbl == true then
return f.tru
elseif tbl == false then
return f.fals
elseif tbl == nil then
return none
end
local tbl_type = type(tbl)
if tbl_type == "string" then
return f.index(tbl)
elseif tbl_type == "number" then
local ik, is, start = 0, step or 1, fpairs and tbl or 1
local stop = not fpairs and tbl or
(type(fpairs) == "number") and fpairs or error("Invalid argument type for 'fpairs': " .. type(fpairs))
if start ~= stop and (is == 0 or ((is < 0) == (start < stop))) then
return none
end
return f.new(function()
local iv = start + ik * is
ik = ik + 1
if start > stop and iv >= stop or
start < stop and iv <= stop or
start == stop and iv == stop then
return ik, iv
end
end)
elseif tbl_type ~= "table" then
error("Invalid argument type for 'tbl': " .. type(tbl))
end
local next, context, k, v = autopairs(tbl, fpairs)
return f.new(function()
k, v = next(context, k)
return k, v
end)
end
---@generic K, V
---@param self F|{ [K]: V }
---@param ... F|{ [K]: V }
---@return F|{ [K]: V }
---@nodiscard
function f:concat(...)
local fsi = 0
local fs = {...}
local sum, last = 0, 0
for i = 1, #fs do
fs[i] = is_f(fs[i]) and fs[i] or f.from(fs[i])
end
return f.new(function()
if fsi == 0 then
local k, v = self:next()
last = type(k) == "number" and math.max(k, last) or last
if k ~= nil then
return k, v
end
fsi, sum, last = 1, last, 0
end
while fsi <= #fs do
local k, v = fs[fsi]:next()
last = type(k) == "number" and math.max(k, last) or last
if k ~= nil then
return type(k) == "number" and k + sum or k, v
end
fsi, sum, last = fsi + 1, sum + last, 0
end
end)
end
---@generic K, V, U
---@param self F|{ [K]: V }
---@param func F|fun(v: V, k: K): U
---@return F|{ [K]: U }
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U }
function f:map(func)
func = type(func) == "string" and f.index(func) or func
return f.new(function()
local k, v = self:next()
if k ~= nil then
return k, func(v, k)
end
end)
end
---@generic K, V, U
---@param self F|{ [K]: V }
---@param func F|fun(v: V, k: K): { [any]: U }
---@param fpairs? fun(t: table<K, V>): (fun(table: table<K, V>, index?: K): K, V)
---@return F|{ [K]: U }
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: U }
function f:flatmap(func, fpairs)
-- local i = 0
local vt, vk, vv, vp
func = type(func) == "string" and f.index(func) or func
return f.new(function()
if vk then
vk, vv = vp(vt, vk)
if vk ~= nil then
-- i = i + 1
-- return i, vv
return vk, vv
end
end
while true do
local k, v = self:next()
if k == nil then
return
end
v = func(v, k)
if type(v) ~= "table" then
-- i = i + 1
-- return i, v
return k, v
end
vp, vt, vk = autopairs(v, fpairs)
vk, vv = vp(vt, vk)
if vk ~= nil then
-- i = i + 1
-- return i, vv
return vk, vv
end
end
end)
end
---@generic K, V
---@param self F|{ [K]: V }
---@param func F|fun(v: V, k: K): boolean
---@return F|{ [K]: V }
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): F|{ [K]: V }
function f:where(func)
func = type(func) == "string" and f.index(func) or func
return f.new(function()
local k, v
while true do
k, v = self:next()
if k == nil then
return
end
if func(v, k) then
return k, v
end
end
end)
end
---@generic K, V
---@param self F|{ [K]: V }
---@return F|{ [integer]: K }
---@nodiscard
function f:keys()
local i = 0
return f.new(function()
local k = self:next()
if k ~= nil then
i = i + 1
return i, k
end
end)
end
---@generic K, V
---@param self F|{ [K]: V }
---@return F|{ [integer]: V }
---@nodiscard
function f:values()
local i = 0
return f.new(function()
local k, v = self:next()
if k ~= nil then
i = i + 1
return i, v
end
end)
end
---@generic K, V
---@param self F|{ [K]: V }
---@param skip? integer
---@param take? integer
---@return F|{ [K]: V }
---@nodiscard
function f:slice(skip, take)
if (not skip or skip <= 0) and not take then
return self
end
local i = 0
return f.new(function()
if take and take <= 0 then
return
end
while skip and skip > 0 do
skip = skip - 1
self:next()
end
local k, v = self:next()
if k ~= nil and (not take or i < take) then
i = i + 1
return i, v
end
end)
end
---@generic K, V
---@param self F|{ [K]: V }
---@param n? integer
---@return F|{ [K]: V }
---@nodiscard
function f:skip(n)
return self:slice(n)
end
---@generic K, V
---@param self F|{ [K]: V }
---@param n? integer
---@return F|{ [K]: V }
---@nodiscard
function f:take(n)
return self:slice(0, n)
end
---@generic K, V, A
---@param self F|{ [K]: V }
---@param seed A
---@param func fun(a: A, v: V, k: K): A
---@return A
---@overload fun(self: F|{ [K]: V }, seed: fun(a: A, v: V, k: K): A): A
---@nodiscard
function f:fold(seed, func)
local k, v
if not func then
func = seed
k, seed = self:next()
if k == nil then
return seed
end
end
while true do
k, v = self:next()
if k == nil then
return seed
end
seed = func(seed, v, k)
end
end
---@generic K, V
---@param self F|{ [K]: V }
---@param func F|fun(v: V, k: K): boolean
---@return boolean|V
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): boolean
function f:any(func)
func = type(func) == "string" and f.index(func) or func
for k, v in self.next do
if not func or func(v, k) then
return v or true
end
end
return false
end
---@generic K, V
---@param self F|{ [K]: V }
---@param func F|fun(v: V, k: K): boolean
---@return boolean|V
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): boolean
function f:all(func)
func = type(func) == "string" and f.index(func) or func
for k, v in self.next do
if not func or not func(v, k) then
return v and false
end
end
return true
end
---@generic K, V
---@param self F|{ [K]: V }
---@param func F|fun(v: V, k: K): boolean
---@return integer
---@nodiscard
---@overload fun(self: F|{ [K]: V }, func: string): integer
function f:count(func)
local ret = 0
func = type(func) == "string" and f.index(func) or func
for k, v in self.next do
if not func or func(v, k) then
ret = ret + 1
end
end
return ret
end
---@generic K, V
---@param self F|{ [K]: V }
---@return string
---@nodiscard
function f:string()
local k, v = self:next()
if k == nil then
return "{}"
end
local ret = "{" .. (type(k) == "number" and "" or tostring(k) .. ": ") .. tostring(v)
while true do
k, v = self:next()
if k == nil then
return ret .. "}"
end
ret = ret .. ", " .. (type(k) == "number" and tostring(v) or tostring(k) .. ": " .. tostring(v))
end
end
---@generic K, V
---@param self F|{ [K]: V }
---@return { [K]: V }
---@nodiscard
function f:table()
local ret = {}
for k, v in self.next do
ret[k] = v
end
return ret
end
---@generic K, V
---@param self F|{ [K]: V }
---@param func? fun(v: V, k: K)
function f:each(func)
for k, v in self.next do
if func then
func(v, k)
end
end
end
none = f.new()
return f.from