Modul:type guard
Penampilan
- Laman modul ini kekurangan sublaman pendokumenan. Sila cipta laman pendokumenan tersebut.
- Pautan berguna: senarai sublaman • pautan • transklusi • kes ujian • kotak pasir
local export = {}
--[==[ intro:
This module provides utilities for type guarding functions and creating classes
and enums.
]==]
local error = error
local ipairs = ipairs
local pairs = pairs
local setmetatable = setmetatable
local sub = string.sub
local type = type
local unpack = unpack or table.unpack
local types = {}
-- Defaults for types.
local type_defaults = {
["string"] = "",
["number"] = 0,
["table"] = {},
["function"] = function() end,
["boolean"] = false,
}
-- Built-in types.
local builtins = {}
builtins["nil"] = true
for k, _ in pairs(type_defaults) do
builtins[k] = true
end
-- Parse user-defined type into table of possible types.
local function eval_type(t)
local ret = {}
if sub(t, -1) == "?" then
ret["nil"] = true
t = sub(t, 1, -2)
end
for v in string.gmatch(t, "([^|]+)") do
ret[v] = true
end
return ret
end
--[==[
Same as the built-in {type} function, except checks to see if the value matches
an existing class in the type store.
]==]
function export.type(value)
local luatype = type(value)
if luatype == "table" then
local class = value._typing_class
if class then
if types[class] then
return class
else
error("Attempt to use nonexistent class " .. class .. ".")
end
end
local enum = value._typing_enum
if enum then
if types[enum] then
return enum
else
error("Attempt to use nonexistent enum " .. enum .. ".")
end
end
end
return luatype
end
local special_type = export.type
--[==[
Return a type guard for a function `fun`. `types` should be an array of the
expected types to be returned by {export.type}, in the same order of the
arguments of the function. Example usage:
```
local m_type = require("Module:type guard")
local function say_hi(to_whom)
mw.log("Hello, " .. to_whom .. "!")
end
say_hi = m_type.guard(say_hi, {"string"})
say_hi(1) --> Expected argument 1 to be of type `string`, but received `number`.
say_hi("John") --> Hello, John!
```
]==]
function export.guard(fun, types)
if not types then
return fun
end
return function(...)
local args = {...}
for i, a in ipairs(args) do
local expected = types[i]
local eval = eval_type(expected)
local actual = special_type(a)
if not eval[actual] then
error("Expected argument " .. i .. " to be of type `" ..
expected .. "`, but received `" ..
actual .. "`.")
elseif actual == "table" and a._typing_enum then
args[i] = a._item
end
end
return fun(unpack(args))
end
end
--[==[
Instantiate a new class with the given name and properties and save it to the
type store. A constructor will be automatically generated based on the given
properties, which can then be optionally wrapped in a method to handle more
complex initialisation logic. The default {__call} method can be used like this:
```
local m_type = require("Module:type guard")
local Person = m_type.class("Person", { name = "string", age = "number" })
local instance = Person {
name = "John",
age = 20,
}
mw.logObject(instance)
mw.logObject(getmetatable(instance))
```
The expected output is:
```
table#1 {
metatable = table#2,
["age"] = 20,
["name"] = "John",
}
table#1 {
["__index"] = table#2 {
["_typing_class"] = "Person",
["age"] = 0,
["name"] = "",
},
}
```
]==]
function export.class(name, properties)
if builtins[name] then
error("Class " .. name .. " has the same name as a built-in type.")
end
local mt = {}
local ret = {}
local prop_types = {}
local i = 1
for k, t in pairs(properties) do
prop_types[k] = eval_type(t)
ret[k] = type_defaults[t] or nil
i = i + 1
end
ret._typing_class = name
mt.__call = function(self, args)
local r = {}
for k, def in pairs(prop_types) do
local val = args[k]
if val == nil then
if not def["nil"] then
error("Missing required property `" .. k .. "`.")
end
else
local actual_t = special_type(val)
if not def[actual_t] then
error("Expected property `" .. k .. "` to be of type `"
.. properties[k] .. "`, but received `"
.. actual_t .. "`.")
end
end
r[k] = val
end
return setmetatable(r, {
__index = self,
})
end
setmetatable(ret, mt)
types[name] = true
return ret
end
--[==[
Create a plain enum with the keys `keys` and save it to the type store with name
`name`.
Note that the enum items are actually tables containing the number and
typing information; if your function is just expecting the number, you should
type guard it with {export.guard} before passing your enum in.
]==]
function export.enum(name, keys)
if builtins[name] then
error("Class " .. name .. " has the same name as a built-in type.")
end
local ret = {}
local mt = {
__eq = function(a, b)
return a._item == b
end,
}
for i, k in ipairs(keys) do
local item = {
_typing_enum = name,
_item = i,
}
setmetatable(item, mt)
ret[k] = item
end
types[name] = true
return ret
end
return export