-
Notifications
You must be signed in to change notification settings - Fork 1
/
LowerClass.lua
231 lines (195 loc) · 7.2 KB
/
LowerClass.lua
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
-- -------------------------------------------------------------------------- --
-- Class Setup --
-- -------------------------------------------------------------------------- --
-- Define LowerClass with local weak tables for method storage
---@class lowerclass
---@overload fun(name: string, ...): Class|function
local lowerclass = {}
-- -------------------------------------------------------------------------- --
-- Core --
-- -------------------------------------------------------------------------- --
-- Weak tables for storing method data
local classData = setmetatable({}, { __mode = "k" }) -- Stores class data
-- -------------------------------------------------------------------------- --
-- Class Variable Declaration --
-- -------------------------------------------------------------------------- --
--- A very fancy way to create a better __index for a class
--- @param aClass Class
--- @param var table|function
--- @return table|function
local function __createIndexWrapper(aClass, var)
if var == nil then
return classData[aClass].lookupDict
elseif type(var) == "function" then
return function(self, name)
return var(self, name) or classData[aClass].lookupDict[name]
end
else -- if type(f) == "table" then
return function(self, name)
return var[name] or classData[aClass].lookupDict[name]
end
end
end
--- Ensures a variable is propegated to all subclasses
--- @param aClass Class
--- @param name string
--- @param var any
local function __propegateClassVariable(aClass, name, var)
var = name == "__index" and __createIndexWrapper(aClass, var) or var
classData[aClass].lookupDict[name] = var
for _, child in ipairs(classData[aClass].heirarchyData.children) do
if classData[child].definedVariables[name] == nil then
__propegateClassVariable(child, name, var)
end
end
end
-- Adds / Removes a variable from a class
---@param aClass Class
---@param name string
---@param var any
local function __declareClassVariable(aClass, name, var)
-- print("Declared " .. tostring(aClass) .. "." .. tostring(name) .. " as " .. tostring(var))
-- Set the var internally first
local dat = classData[aClass]
dat.definedVariables[name] = var
if var == nil then
for _, parent in ipairs(dat.heirarchyData.parents) do
if parent[name] ~= nil then
var = parent
break
end
end
end
__propegateClassVariable(aClass, name, var)
end
-- -------------------------------------------------------------------------- --
-- Inheritance / Mixins --
-- -------------------------------------------------------------------------- --
-- Adds a parent to a class
---@param aClass Class
---@param parent Class
local function __addParent(aClass, parent)
table.insert(classData[aClass].heirarchyData.parents, parent)
table.insert(classData[parent].heirarchyData.children, aClass)
for key, value in pairs(classData[parent].definedVariables) do
if not (key == "__index" and type(value) == "table") then
__propegateClassVariable(aClass, key, value)
end
end
end
-- Checks if the passed object is an instance of a class
---@param self Class object to check
---@param aClass Class class to check against
---@return boolean
local function __is(self, aClass)
-- If instance, extract class
---@diagnostic disable-next-line: undefined-field
self = self.class or self
if self == aClass then
return true
end
local dat = classData[self]
for _, parent in ipairs(dat.heirarchyData.parents) do
if __is(parent, aClass) then
return true
end
end
return false
end
-- Adds a mixin to a class
---@param aClass Class
---@param mixin table
local function __addMixin(aClass, mixin)
for name, method in pairs(mixin) do
if name ~= "included" then
aClass[name] = method
end
end
if type(mixin.included) == "function" then
mixin:included(aClass)
end
end
-- -------------------------------------------------------------------------- --
-- Table Creation --
-- -------------------------------------------------------------------------- --
--- Creates an instance of a class
--- @generic T
--- @param aClass T
--- @return T
local function __newInstance(aClass, ...)
local instance = setmetatable({
__type = aClass.name,
class = aClass,
include = __addMixin,
}, classData[aClass].lookupDict)
if instance.__init then
instance:__init(...)
end
return instance
end
--- Creates a new class
--- @param name string
--- @return Class
local function __createClass(name, ...)
local lookupDict = {}
lookupDict.__index = lookupDict
-- Generate class object
---@class Class
local aClass = setmetatable({
__type = "class",
name = name,
include = function(self, ...)
-- If mixin is not registered as a class, use addMixin, otherwise use addParent
for _, mixin in ipairs({ ... }) do
if type(mixin) == "function" then
mixin = mixin(self)
end
assert(type(mixin) == "table", "mixin must be a table")
local func = classData[mixin] == nil and __addMixin or __addParent
func(self, mixin)
end
end,
new = __newInstance,
}, {
__index = lookupDict,
__tostring = function()
return "class(\"" .. name .. "\")"
end,
__newindex = __declareClassVariable,
__call = __newInstance
})
-- Generate internal class data
classData[aClass] = {
definedVariables = {},
lookupDict = lookupDict,
heirarchyData = {
children = {},
parents = {},
}
}
-- Finalize setup by adding `is` method and all mixins
aClass.is = __is
aClass:include(...)
return aClass
end
-- -------------------------------------------------------------------------- --
-- LowerClass Setup --
-- -------------------------------------------------------------------------- --
---@diagnostic disable-next-line: param-type-mismatch
setmetatable(lowerclass, {
__call = function(self, name, ...)
return __createClass(name, ...)
end,
})
-- Setup new method
lowerclass.new = function(self, name, ...)
return __createClass(name, ...)
end
lowerclass.is = function(obj, aClass)
return type(obj) == "table" and type(obj.is) == "function" and obj:is(aClass)
end
local __type = type
lowerclass.type = function(obj)
return __type(obj) == "table" and obj.__type or __type(obj)
end
return lowerclass