· 8 years ago · May 14, 2018, 12:22 AM
1local argcheck = require 'argcheck'
2local argcheckenv = require 'argcheck.env'
3local doc = require 'argcheck.doc'
4local ffi
5
6pcall(function()
7 ffi = require 'ffi'
8 end)
9
10doc[[
11Object Classes for Lua
12----------------------
13This package provide simple object-oriented capabilities to Lua.
14Each class is defined with a metatable, which contains methods.
15Inheritance is achieved by setting metatables over metatables.
16An efficient type checking is provided.
17## Typical Example
18```lua
19local class = require 'class'
20-- define some dummy A class
21local A = class('A')
22function A:__init(stuff)
23 self.stuff = stuff
24end
25function A:run()
26 print(self.stuff)
27end
28-- define some dummy B class, inheriting from A
29local B = class('B', 'A')
30function B:__init(stuff)
31 A.__init(self, stuff) -- call the parent init
32end
33function B:run5()
34 for i=1,5 do
35 print(self.stuff)
36 end
37end
38-- create some instances of both classes
39local a = A('hello world from A')
40local b = B('hello world from B')
41-- run stuff
42a:run()
43b:run()
44b:run5()
45```
46## Documentation
47First, require the package
48```lua
49local class = require 'class'
50```
51Note that `class` does not clutter the global namespace.
52Class metatables are then created with `class(name)` or equivalently `class.new(name)`.
53```lua
54local A = class('A')
55local B = class('B', 'A') -- B inherit from A
56```
57You then have to fill-up the returned metatable with methods.
58```lua
59function A:myMethod()
60 -- do something
61end
62```
63There are two special methods: `new()`, which already exists when the class is created and _should not be overrided_
64and `__init()` which is called by `new()` at the creation of the class.
65```lua
66function A:__init(args)
67 -- do something with args
68 -- note that self exists
69end
70```
71Creation of an instance is then achieved with the `new()` function or (equivalently) using the Lua `__call` metamethod:
72```lua
73local a = A('blah blah') -- an instance of A
74local aa = A.new('blah blah') -- equivalent of the above
75```
76]]
77
78local class = {}
79local classes = {}
80local isofclass = {}
81local ctypes = {}
82
83-- create a constructor table
84local function constructortbl(metatable)
85 local ct = {}
86 setmetatable(ct, {
87 __index=metatable,
88 __newindex=metatable,
89 __metatable=metatable,
90 __call=function(self, ...)
91 return self.new(...)
92 end
93 })
94 return ct
95end
96
97class.new = argcheck{
98 doc = [[
99### `class.new(@ARGP)`
100Creates a new class called `name`, which might optionally inherit from `parentname`.
101Returns a table, in which methods should be defined.
102Note that the returned table is not the metatable, but a _constructor_ table (with a `__call`
103function defined). In that respect, one can use the following shorthand:
104```lua
105local A = class.new('A')
106local a = A.new() -- instance.
107local aa = A() -- another instance (shorthand).
108```
109There is also a shorthand `class.new()`, which is `class()`.
110]],
111 {name="name", type="string", doc="class name"},
112 {name="parentname", type="string", opt=true, doc="parent class name"},
113 {name="ctype", type="cdata", opt=true, doc="ctype which should be considered as of class name"},
114 call =
115 function(name, parentname, ctype)
116 local class = {__typename = name}
117
118 assert(not classes[name], string.format('class <%s> already exists', name))
119 if ctype then
120 local ctype_id = tonumber(ctype)
121 assert(ctype_id, 'invalid ffi ctype')
122 assert(not ctypes[ctype_id], string.format('ctype <%s> already considered as <%s>', tostring(ctype), ctypes[ctype_id]))
123 ctypes[ctype_id] = name
124 end
125
126 class.__index = class
127
128 class.__factory =
129 function()
130 local self = {}
131 setmetatable(self, class)
132 return self
133 end
134
135 class.__init =
136 function()
137 end
138
139 class.new =
140 function(...)
141 local self = class.__factory()
142 self:__init(...)
143 return self
144 end
145
146 classes[name] = class
147 isofclass[name] = {[name]=true}
148
149 if parentname then
150 local parent = classes[parentname]
151 assert(parent, string.format('parent class <%s> does not exist', parentname))
152 setmetatable(class, parent)
153
154 -- consider as type of parent
155 while parent do
156 isofclass[parent.__typename][name] = true
157 parent = getmetatable(parent)
158 end
159
160 return constructortbl(class), classes[parentname]
161 else
162 return constructortbl(class)
163 end
164 end
165}
166
167class.factory = argcheck{
168 doc = [[
169### `class.factory(name)`
170Return a new (empty) instance of the class `name`. No `__init` method will be called.
171]],
172 {name="name", type="string"},
173 call =
174 function(name)
175 assert(classes[name], string.format('unknown class <%s>', name))
176 return class[name].__factory()
177 end
178}
179
180class.metatable = argcheck{
181 doc = [[
182### `class.metatable(name)`
183Return the metatable (i.e. the table containing all methods) related to class `name`.
184]],
185 {name="name", type="string"},
186 call =
187 function(name)
188 return classes[name]
189 end
190}
191
192doc[[
193### `class.type(obj)`
194Return the type of the object `obj` (if this is a known class), or the type
195returned by the standard lua `type()` function (if it is not known).
196]]
197
198function class.type(obj)
199 local tname = type(obj)
200
201 local objname
202 if tname == 'cdata' then
203 objname = ctypes[tonumber(ffi.typeof(obj))]
204 elseif tname == 'userdata' or tname == 'table' then
205 local mt = getmetatable(obj)
206 if mt then
207 objname = rawget(mt, '__typename')
208 end
209 end
210
211 if objname then
212 return objname
213 else
214 return tname
215 end
216end
217
218doc[[
219### `class.istype(obj, name)`
220Check is `obj` is an instance (or a child) of class `name`. Returns a boolean.
221]]
222
223function class.istype(obj, typename)
224 local tname = type(obj)
225
226 local objname
227 if tname == 'cdata' then
228 objname = ctypes[tonumber(ffi.typeof(obj))]
229 elseif tname == 'userdata' or tname == 'table' then
230 local mt = getmetatable(obj)
231 if mt then
232 objname = rawget(mt, '__typename')
233 end
234 end
235
236 if objname then -- we are now sure it is one of our object
237 local valid = rawget(isofclass, typename)
238 if valid then
239 return rawget(valid, objname) or false
240 else
241 return objname == typename -- it might be some other type system
242 end
243 else
244 return tname == typename
245 end
246end
247
248-- make sure argcheck understands those types
249argcheckenv.istype = class.istype
250
251-- allow class() instead of class.new()
252setmetatable(class, {__call=
253 function(self, ...)
254 return self.new(...)
255 end})
256
257return class