239 lines
6.7 KiB
Lua
239 lines
6.7 KiB
Lua
---@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.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.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
|