-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathclass.lua
More file actions
185 lines (154 loc) · 3.1 KB
/
Copy pathclass.lua
File metadata and controls
185 lines (154 loc) · 3.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
---@param t table
---@param ... any
---@return any
local function return_from_new(t, ...)
if select("#", ...) > 0 then
return ...
end
return t
end
---@param T {new: function?}
---@param ... any
---@return table
local function new(T, ...)
local T_new = T.new
if not T_new then
local t = ... or {}
assert(type(t) == "table" and not getmetatable(t), "bad argument to default constructor")
return setmetatable(t, T)
end
local t = setmetatable({}, T)
return return_from_new(t, T_new(t, ...))
end
---@param T table
---@return boolean
local function is_class(T)
local mt = getmetatable(T)
return not not (mt and mt.__is_class)
end
---@param t table
---@return boolean
local function is_instance(t)
local T = getmetatable(t)
return is_class(T)
end
---@param T table
---@param _T table
---@return boolean
local function type_of_class(T, _T)
local mt = getmetatable(_T)
if not mt or not mt.__is_class then
return false
end
return mt.__parents[T] or false
end
---@param T table
---@param t table
---@return boolean
local function type_of_instance(T, t)
local _T = getmetatable(t)
if not _T then
return false
end
return type_of_class(T, _T)
end
---@param p table?
---@param t table?
---@return table
local function class(p, t)
if p then
assert(is_class(p), "bad argument #1 to 'class'")
end
local mt = {
__call = new,
__add = class,
__mul = type_of_instance,
__div = type_of_class,
__is_class = true,
}
local T = {}
T.__index = T
T.__name = debug.getinfo(2, "S").source
---@type {[table]: true?}
local parents = {[T] = true}
if p then
---@type {[table]: true}
local parent_classes = getmetatable(p).__parents
for parent in pairs(parent_classes) do
parents[parent] = true
end
end
if not is_class(t) then
mt.__index = p
mt.__parents = parents
return setmetatable(T, mt)
end
---@type {[table]: true}
local parent_classes = getmetatable(t).__parents
for parent in pairs(parent_classes) do
parents[parent] = true
end
mt.__parents = parents
mt.__indexes = {p, t}
function mt.__index(_, k)
---@type any, any
local a, b = p[k], t[k]
if a ~= nil then
return a
end
return b
end
return setmetatable(T, mt)
end
-- tests
do
local A = class()
assert(is_class(A))
assert(not is_instance(A))
local a = A() ---@type any
assert(is_instance(a))
assert(not is_class(a))
assert(A / A)
assert(not (A * A))
assert(A * a)
assert(not (A / a))
end
do
local A = class()
local B = A + {} ---@type any
local a = A() ---@type any
local b = B() ---@type any
assert(A / A)
assert(A / B)
assert(B / B)
assert(not (B / A))
assert(A * a)
assert(A * b)
assert(B * b)
assert(not (B * a))
end
do
local A = class()
local B = class()
local C = class()
local X = A + B + C ---@type any
local x = X() ---@type any
assert(A / X)
assert(B / X)
assert(C / X)
assert(X * x)
assert(not (X / A))
assert(not (X / B))
assert(not (X / C))
assert(not (x * X))
assert(not (X / x))
end
local M = {
is_class = is_class,
is_instance = is_instance,
}
setmetatable(M, {__call = function(_, p, t)
return class(p, t)
end})
---@cast M +fun(p: table?, t: table?): table
return M