Modul:type guard
Tampaian
Dokumentasi untuk modul ini dapat dibuat di Modul:type guard/doc
-- Imported from enwiktionary
-- Source: https://en.wiktionary.org/wiki/Module:type guard
-- License: CC BY-SA
local export = {}
--[==[ intro:
This module provides utilities for type guarding functions and creating classes
and enums.
]==]
local error = error
local format = string.format
local gmatch = string.gmatch
local ipairs = ipairs
local pairs = pairs
local rawset = rawset
local setmetatable = setmetatable
local sub = string.sub
local type = type
local unpack = unpack or table.unpack
local types = {
["nil"] = true,
["string"] = true,
["number"] = true,
["table"] = true,
["function"] = true,
["boolean"] = true,
}
local errors = {
already_exists = "type '%s' already exists",
bad_argument = "bad argument #%d (expected '%s', found '%s')",
bad_property = "bad property '%s' (expected '%s', found '%s')",
nonexistent_prop = "property '%s' does not exist on class '%s'",
nonexistent_type = "attempt to use nonexistent %s '%s'",
}
-- 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 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 or enum in the type store.
]==]
function export.type(value)
local lua_type = type(value)
if lua_type == "table" then
local class = value._typing_class
if class then
if types[class] then
return class
else
error(format(errors.nonexistent_type, "class", class))
end
end
local enum = value._typing_enum
if enum then
if types[enum] then
return enum
else
error(format(errors.nonexistent_type, "enum", enum))
end
end
end
return lua_type
end
local special_type = export.type
--[==[
Return a type guard for a function `fun`. `arg_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 guard = require("Module:type guard").guard
local function say_hi(to_whom)
mw.log("Hello, " .. to_whom .. "!")
end
say_hi = guard(say_hi, {"string"})
say_hi(1) --> bad argument #1 (expected 'string', found 'number')
say_hi("John") --> Hello, John!
```
]==]
function export.guard(fun, arg_types)
if not arg_types then
return fun
end
return function(...)
local args = { ... }
for i, a in ipairs(args) do
local expected = arg_types[i]
if not expected then
break -- bail out, could be inside varargs
end
local eval = eval_type(expected)
local actual = special_type(a)
if not eval[actual] then
error(format(errors.bad_argument, i, expected, actual))
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 in {__call} will be automatically generated based on
the given properties, which can then be used like this:
```
local class = require("Module:type guard").class
local Person = class "Person" {
name = "string",
age = "number",
}
local instance = Person {
name = "John",
age = 20,
}
```
]==]
function export.class(name)
return function(properties)
if types[name] then
error(format(errors.already_exists, name))
end
local mt = {}
local ret = {}
local prop_types = {}
local i = 1
for k, t in pairs(properties) do
prop_types[k] = eval_type(t)
i = i + 1
end
ret._typing_class = name
mt.__call = function(self, args)
local instance = {}
for k, def in pairs(prop_types) do
local val = args[k]
local actual = special_type(val)
if not def[actual] then
error(format(errors.bad_property, k, properties[k], actual))
end
instance[k] = val
end
return setmetatable(instance, {
__index = self,
__newindex = function(self, k, v)
-- allow setting method without having to predefine each one
if type(v) ~= "function" and not properties[k] then
error(format(errors.nonexistent_prop, k, name))
end
rawset(self, k, v)
end,
})
end
setmetatable(ret, mt)
types[name] = true
return ret
end
end
--[==[
Create a plain enum and save it to the type store. Example usage:
```
local enum = require("Module:type guard").enum
local Greeting = enum "Greeting" {
"Hello",
"Goodbye",
}
```
Calling the enum object with an instance of the enum will return the key
of that instance. Using the example above:
```
local instance = Greeting.Hello
local key = Greeting(instance)
mw.log(key) --> Hello
```
]==]
function export.enum(name)
return function(keys)
if types[name] then
error(format(errors.already_exists, name))
end
local ret = {}
for i, k in ipairs(keys) do
local item = {
_typing_enum = name,
_item = i,
}
--[==[
Get the index of an enum item. Example usage:
```
local enum = require("Module:type guard").enum
local Greeting = enum "Greeting" {
"Hello",
"Goodbye",
}
mw.log(Greeting.Hello:index()) --> 1
mw.log(Greeting.Goodbye:index()) --> 2
```
]==]
function item:index()
return self._item
end
ret[k] = item
end
types[name] = true
return setmetatable(ret, {
__call = function(self, i)
for k, v in pairs(self) do
if v._item == i then
return self[k]
end
end
end,
})
end
end
return export