Initial commit
This commit is contained in:
238
lua/cave/meta.lua
Normal file
238
lua/cave/meta.lua
Normal file
@@ -0,0 +1,238 @@
|
||||
---@alias cave.Validator fun(v: any) : boolean
|
||||
|
||||
---@class cave.Meta
|
||||
---@field valid cave.Validator
|
||||
---@field repr string
|
||||
local Meta = {}
|
||||
Meta.__index = Meta
|
||||
|
||||
---@alias cave.MetaLikeItem string|table|cave.Meta
|
||||
---@alias cave.MetaLike cave.MetaLikeItem|cave.MetaLikeItem[]
|
||||
|
||||
---@param repr string
|
||||
---@param valid cave.Validator
|
||||
---@return cave.Meta
|
||||
function Meta.new(repr, valid)
|
||||
vim.validate {
|
||||
repr = { repr, "string" },
|
||||
valid = { valid, "function" },
|
||||
}
|
||||
local meta = setmetatable({}, Meta)
|
||||
meta.valid = valid
|
||||
meta.repr = repr
|
||||
return meta
|
||||
end
|
||||
|
||||
---@param ... cave.MetaLikeItem
|
||||
---@return cave.Meta
|
||||
function Meta.Union(...)
|
||||
local value_valids = {}
|
||||
local value_reprs = {}
|
||||
for idx, value_meta_like in ipairs { ... } do
|
||||
local value_meta = Meta.like(value_meta_like)
|
||||
value_valids[idx] = value_meta.valid
|
||||
value_reprs[idx] = value_meta.repr
|
||||
end
|
||||
local repr = ("Union<%s>"):format(table.concat(value_reprs, ","))
|
||||
local function valid(v)
|
||||
for _, value_valid in pairs(value_valids) do
|
||||
if value_valid(v) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
return Meta.new(repr, valid)
|
||||
end
|
||||
|
||||
---@param value_meta_like cave.MetaLike
|
||||
---@return cave.Meta
|
||||
function Meta.Optional(value_meta_like)
|
||||
local value_meta = Meta.like(value_meta_like)
|
||||
local repr = ("Optional<%s>"):format(value_meta.repr)
|
||||
local function valid(v) return v == nil or value_meta.valid(v) end
|
||||
return Meta.new(repr, valid)
|
||||
end
|
||||
|
||||
---@param mt table
|
||||
---@param name string
|
||||
---@param valid cave.Validator?
|
||||
---@return cave.Meta
|
||||
function Meta.Class(mt, name, valid)
|
||||
assert(type(mt) == "table")
|
||||
local repr = name
|
||||
---@param v any
|
||||
---@return boolean
|
||||
local function default_valid(v)
|
||||
if type(v) ~= "table" then return false end
|
||||
local v_mt = getmetatable(v)
|
||||
while v_mt ~= nil do
|
||||
if v_mt == mt then return true end
|
||||
v_mt = getmetatable(v_mt)
|
||||
end
|
||||
return false
|
||||
end
|
||||
valid = valid or default_valid
|
||||
return Meta.new(repr, valid)
|
||||
end
|
||||
|
||||
---@param tbl table<string,string>
|
||||
---@param name string
|
||||
---@return cave.Meta
|
||||
function Meta.Enum(tbl, name)
|
||||
assert(type(tbl) == "table")
|
||||
local repr = ("Enum<%s>(%s)"):format(name, table.concat(vim.tbl_values(tbl), "|"))
|
||||
local function valid(v) return type(v) == "string" and tbl[v] == v end
|
||||
return Meta.new(repr, valid)
|
||||
end
|
||||
|
||||
---@param value_meta_like cave.MetaLike
|
||||
---@return cave.Meta
|
||||
function Meta.List(value_meta_like)
|
||||
local value_meta = Meta.like(value_meta_like)
|
||||
local repr = ("List<%s>"):format(value_meta.repr)
|
||||
local function valid(vs)
|
||||
if not vim.tbl_islist(vs) then return false end
|
||||
for _, v in ipairs(vs) do
|
||||
if not value_meta.valid(v) then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return Meta.new(repr, valid)
|
||||
end
|
||||
|
||||
---@param key_meta_like cave.MetaLike
|
||||
---@param value_meta_like cave.MetaLike
|
||||
---@return cave.Meta
|
||||
function Meta.Map(key_meta_like, value_meta_like)
|
||||
local key_meta = Meta.like(key_meta_like)
|
||||
local value_meta = Meta.like(value_meta_like)
|
||||
local repr = ("Map<%s,%s>"):format(key_meta.repr, value_meta.repr)
|
||||
local function valid(vs)
|
||||
if type(vs) ~= "table" then return false end
|
||||
for k, v in ipairs(vs) do
|
||||
if not key_meta.valid(k) or value_meta.valid(v) then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return Meta.new(repr, valid)
|
||||
end
|
||||
|
||||
---@param meta_like_tbl table<string, cave.MetaLike>
|
||||
---@param name string
|
||||
---@return cave.Meta
|
||||
function Meta.MapLiteral(meta_like_tbl, name)
|
||||
---@type table<string, cave.Meta>
|
||||
local metas = {}
|
||||
for key, meta_like in meta_like_tbl do
|
||||
metas[key] = Meta.like(meta_like)
|
||||
end
|
||||
---@param vs any
|
||||
---@return boolean
|
||||
local function valid(vs)
|
||||
if type(vs) ~= "table" then return false end
|
||||
for key, meta in pairs(metas) do
|
||||
local value = vs[key]
|
||||
if not meta.valid(value) then return false end
|
||||
end
|
||||
for key, _ in pairs(vs) do
|
||||
if metas[key] == nil then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return Meta.new(name, valid)
|
||||
end
|
||||
|
||||
Meta.Bool = Meta.new("boolean", function(v) return type(v) == "boolean" end)
|
||||
Meta.Function = Meta.new("function", function(v) return type(v) == "function" end)
|
||||
Meta.Number = Meta.new("number", function(v) return type(v) == "number" end)
|
||||
Meta.String = Meta.new("string", function(v) return type(v) == "string" end)
|
||||
Meta.Table = Meta.new("table", function(v) return type(v) == "table" end)
|
||||
Meta.Nil = Meta.new("nil", function(v) return v == nil end)
|
||||
|
||||
local meta_aliases = {
|
||||
["b"] = Meta.Bool,
|
||||
["bool"] = Meta.Bool,
|
||||
["boolean"] = Meta.Bool,
|
||||
["f"] = Meta.Function,
|
||||
["fn"] = Meta.Function,
|
||||
["function"] = Meta.Function,
|
||||
["n"] = Meta.Number,
|
||||
["num"] = Meta.Number,
|
||||
["number"] = Meta.Number,
|
||||
["nil"] = Meta.Nil,
|
||||
["s"] = Meta.String,
|
||||
["str"] = Meta.String,
|
||||
["string"] = Meta.String,
|
||||
["t"] = Meta.Table,
|
||||
["tbl"] = Meta.Table,
|
||||
["table"] = Meta.Table,
|
||||
}
|
||||
|
||||
---@param meta_like cave.MetaLike
|
||||
---@return cave.Meta
|
||||
function Meta.like(meta_like)
|
||||
local meta_like_type = type(meta_like)
|
||||
local meta
|
||||
if meta_like_type == "string" then
|
||||
meta = meta_aliases[meta_like]
|
||||
else
|
||||
assert(meta_like_type == "table")
|
||||
local mt = getmetatable(meta_like)
|
||||
if mt == Meta then
|
||||
meta = meta_like
|
||||
elseif meta_like.__meta ~= nil then
|
||||
meta = meta_like.__meta
|
||||
elseif vim.tbl_islist(meta_like) then
|
||||
meta = Meta.Union(unpack(meta_like))
|
||||
else
|
||||
assert(false)
|
||||
end
|
||||
end
|
||||
assert(getmetatable(meta) == Meta)
|
||||
return meta
|
||||
end
|
||||
|
||||
---@return cave.Validator
|
||||
---@return string
|
||||
function Meta:check() return self.valid, self.repr end
|
||||
|
||||
---@param params table<string,table>
|
||||
function Meta.validate(params)
|
||||
local vim_params = {}
|
||||
for param_name, param_specs in pairs(params) do
|
||||
assert(type(param_name) == "string", "param name is not a string")
|
||||
assert(type(param_specs) == "table", "param spec for '" .. param_name .. "' is not a table")
|
||||
assert(#param_specs == 2, "param spec length for '" .. param_name .. "' is not 2")
|
||||
local param_value = param_specs[1] --[[@as any]]
|
||||
local param_meta_like = param_specs[2] --[[@as cave.MetaLike]]
|
||||
local param_meta = Meta.like(param_meta_like)
|
||||
vim_params[param_name] = { param_value, param_meta:check() }
|
||||
end
|
||||
vim.validate(vim_params)
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@param base_mt table?
|
||||
---@return table
|
||||
function Meta.derive(name, base_mt)
|
||||
Meta.validate { name = { name, Meta.String }, base_mt = { base_mt, Meta.Optional(Meta.Table) } }
|
||||
local mt = {}
|
||||
mt.__index = mt
|
||||
mt.__meta = Meta.Class(mt, name)
|
||||
return (base_mt and setmetatable(mt, base_mt)) or mt
|
||||
end
|
||||
|
||||
---@param obj table
|
||||
---@return string
|
||||
function Meta.get_repr(obj)
|
||||
Meta.validate { obj = { obj, Meta.Table } }
|
||||
local mt = getmetatable(obj)
|
||||
Meta.validate { mt = { mt, Meta.Table } }
|
||||
local meta = mt.__meta
|
||||
Meta.validate { meta = { meta, Meta.Table } }
|
||||
local meta_mt = getmetatable(obj)
|
||||
assert(meta_mt == Meta)
|
||||
---@cast meta cave.Meta
|
||||
return meta.repr
|
||||
end
|
||||
|
||||
return Meta
|
||||
Reference in New Issue
Block a user