1 --- Provides a reuseable and convenient framework for creating classes in Lua.
2 -- Two possible notations:
7 -- The latter form creates a named class within the current environment. Note
8 -- that this implicitly brings in `pl.utils` as a dependency.
10 -- See the Guide for further @{01-introduction.md.Simplifying_Object_Oriented_Programming_in_Lua|discussion}
13 local error, getmetatable, io, pairs, rawget, rawset, setmetatable, tostring, type =
14 _G.error, _G.getmetatable, _G.io, _G.pairs, _G.rawget, _G.rawset, _G.setmetatable, _G.tostring, _G.type
17 -- this trickery is necessary to prevent the inheritance of 'super' and
18 -- the resulting recursive call problems.
19 local function call_ctor (c,obj,...)
20 -- nice alias for the base class ctor
21 local base = rawget(c,'_base')
23 local parent_ctor = rawget(base,'_init')
24 while not parent_ctor do
25 base = rawget(base,'_base')
26 if not base then break end
27 parent_ctor = rawget(base,'_init')
30 rawset(obj,'super',function(obj,...)
31 call_ctor(base,obj,...)
35 local res = c._init(obj,...)
36 rawset(obj,'super',nil)
40 --- initializes an __instance__ upon creation.
41 -- @function class:_init
42 -- @param ... parameters passed to the constructor
43 -- @usage local Cat = class()
44 -- function Cat:_init(name)
45 -- --self:super(name) -- call the ancestor initializer if needed
49 -- local pussycat = Cat("pussycat")
50 -- print(pussycat.name) --> pussycat
52 --- checks whether an __instance__ is derived from some class.
53 -- Works the other way around as `class_of`. It has two ways of using;
54 -- 1) call with a class to check against, 2) call without params.
55 -- @function instance:is_a
56 -- @param some_class class to check against, or `nil` to return the class
57 -- @return `true` if `instance` is derived from `some_class`, or if `some_class == nil` then
58 -- it returns the class table of the instance
59 -- @usage local pussycat = Lion() -- assuming Lion derives from Cat
60 -- if pussycat:is_a(Cat) then
61 -- -- it's true, it is a Lion, but also a Cat
64 -- if pussycat:is_a() == Lion then
67 local function is_a(self,klass)
69 -- no class provided, so return the class this instance is derived from
70 return getmetatable(self)
72 local m = getmetatable(self)
73 if not m then return false end --*can't be an object!
75 if m == klass then return true end
81 --- checks whether an __instance__ is derived from some class.
82 -- Works the other way around as `is_a`.
83 -- @function some_class:class_of
84 -- @param some_instance instance to check against
85 -- @return `true` if `some_instance` is derived from `some_class`
86 -- @usage local pussycat = Lion() -- assuming Lion derives from Cat
87 -- if Cat:class_of(pussycat) then
90 local function class_of(klass,obj)
91 if type(klass) ~= 'table' or not rawget(klass,'is_a') then return false end
92 return klass.is_a(obj,klass)
95 --- cast an object to another class.
96 -- It is not clever (or safe!) so use carefully.
97 -- @param some_instance the object to be changed
98 -- @function some_class:cast
99 local function cast (klass, obj)
100 return setmetatable(obj,klass)
104 local function _class_tostring (obj)
105 local mt = obj._class
106 local name = rawget(mt,'_name')
107 setmetatable(obj,nil)
108 local str = tostring(obj)
110 if name then str = name ..str:gsub('table','') end
114 local function tupdate(td,ts,dont_override)
115 for k,v in pairs(ts) do
116 if not dont_override or td[k] == nil then
122 local function _class(base,c_arg,c)
123 -- the class `c` will be the metatable for all its objects,
124 -- and they will look up their methods in it.
125 local mt = {} -- a metatable for the class to support __call and _handler
126 -- can define class by passing it a plain table of methods
127 local plain = type(base) == 'table' and not getmetatable(base)
135 if type(base) == 'table' then
136 -- our new class is a shallow copy of the base class!
137 -- but be careful not to wipe out any methods we have been given at this point!
138 tupdate(c,base,plain)
140 -- inherit the 'not found' handler, if present
141 if rawget(c,'_handler') then mt.__index = c._handler end
142 elseif base ~= nil then
143 error("must derive from a table type",3)
152 if base and rawget(base,'_class_init') then
153 base._class_init(c,c_arg)
156 -- expose a ctor which can be called by <classname>(<args>)
157 mt.__call = function(class_tbl,...)
159 if rawget(c,'_create') then obj = c._create(...) end
160 if not obj then obj = {} end
163 if rawget(c,'_init') then -- explicit constructor
164 local res = call_ctor(c,obj,...)
165 if res then -- _if_ a ctor returns a value, it becomes the object...
169 elseif base and rawget(base,'_init') then -- default constructor
170 -- make sure that any stuff from the base class is initialized!
171 call_ctor(base,obj,...)
174 if base and rawget(base,'_post_init') then
180 -- Call Class.catch to set a handler for methods/properties not found in the class!
181 c.catch = function(self, handler)
182 if type(self) == "function" then
183 -- called using . instead of :
190 c.class_of = class_of
194 if not rawget(c,'__tostring') then
195 c.__tostring = _class_tostring
201 --- create a new class, derived from a given base class.
202 -- Supporting two class creation syntaxes:
203 -- either `Name = class(base)` or `class.Name(base)`.
204 -- The first form returns the class directly and does not set its `_name`.
205 -- The second form creates a variable `Name` in the current environment set
206 -- to the class, and also sets `_name`.
208 -- @param base optional base class
209 -- @param c_arg optional parameter to class constructor
210 -- @param c optional table to be used as class
212 class = setmetatable({},{
213 __call = function(fun,...)
216 __index = function(tbl,key)
217 if key == 'class' then
218 io.stderr:write('require("pl.class").class is deprecated. Use require("pl.class")\n')
221 compat = compat or require 'pl.compat'
222 local env = compat.getfenv(2)
224 local c = _class(...)
232 class.properties = class()
234 function class.properties._class_init(klass)
235 klass.__index = function(t,key)
236 -- normal class lookup!
238 if v then return v end
240 v = rawget(klass,'get_'..key)
245 return rawget(t,'_'..key)
247 klass.__newindex = function (t,key,value)
248 -- if there's a setter, use that, otherwise directly set table
249 local p = 'set_'..key
250 local setter = klass[p]