mirror of
https://github.com/Reuh/classtoi.git
synced 2025-10-27 12:19:31 +00:00
Rename classtoi to classtoi-heavy; replace with new classtoi
This commit is contained in:
parent
3bb90f9034
commit
f43867739b
6 changed files with 432 additions and 224 deletions
|
|
@ -1,3 +1,12 @@
|
||||||
|
classtoi: more features than class-toi light; everything is just as fast except class creation.
|
||||||
|
1.0.0:
|
||||||
|
- Initial version.
|
||||||
|
|
||||||
|
classtoi-light: only the minimum features.
|
||||||
|
1.0.0:
|
||||||
|
- Initial version.
|
||||||
|
|
||||||
|
classtoi-heavy: all the features and the slowness, not recommended anymore.
|
||||||
0.1.4:
|
0.1.4:
|
||||||
- :new non-nil custom returns values now replace the usual returned instance
|
- :new non-nil custom returns values now replace the usual returned instance
|
||||||
- Custom inherited __index metamethods are now called on the correct class instead of the class it is defined in.
|
- Custom inherited __index metamethods are now called on the correct class instead of the class it is defined in.
|
||||||
|
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright 2019 Étienne "Reuh" Fildadut
|
Copyright 2024 Étienne "Reuh" Fildadut
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
|
|
||||||
185
classtoi-heavy.lua
Normal file
185
classtoi-heavy.lua
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
--- Reuh's class library version 0.1.4. Lua 5.1-5.3 and LuaJit compatible.
|
||||||
|
-- Objects and classes behavior are identical, so you can consider this to be somewhat prototype-based.
|
||||||
|
-- Features:
|
||||||
|
-- * Multiple inheritance with class(parents...) or someclass(newstuff...)
|
||||||
|
-- * Every metamethods supported
|
||||||
|
-- * Everything in a class can be redefined (and will be usable in an object) (except __super)
|
||||||
|
-- * Preserve parents metamethods if already set
|
||||||
|
-- * Instanciate with class:new(...)
|
||||||
|
-- * Test inheritance relations with class/object.is(thing, isThis)
|
||||||
|
-- * Call object:new(...) on instanciation
|
||||||
|
-- * If object:new(...) returns non-nil values, they will be returned instead of the instance
|
||||||
|
-- * Call class.__inherit(class, inheritingClass) when creating a class inheriting the previous class. If class.__inherit returns a value, it will
|
||||||
|
-- be used as the parent table instead of class, allowing some pretty fancy behavior (it's like an inheritance metamethod).
|
||||||
|
-- * Implements Class Commons
|
||||||
|
-- * I don't like to do this, but you can redefine every field and metamethod after class creation (except __index and __super).
|
||||||
|
-- Not features / Things you may want to know:
|
||||||
|
-- * Will set the metatable of all parent classes/tables if no metatable is set (the table will be its own metatable).
|
||||||
|
-- * You can't redefine __super (any __super you define will be only avaible by searching in the default __super contents).
|
||||||
|
-- * Redefining __super or __index after class creation will break everything (though it should be ok with new, is, __call and everything else).
|
||||||
|
-- * When creating a new class, the methods new, is, __call, __index and __super will always be redefined, so trying to get theses fields
|
||||||
|
-- will return the default method and not the one you've defined. However, theses defaults will be replaced by yours automatically on instanciation,
|
||||||
|
-- except __super and __index, but __index should call your __index and act like you expect. __super will however always be the default one
|
||||||
|
-- and doesn't proxy in any way yours.
|
||||||
|
-- * __index metamethods will be called with an extra third argument, which is the current class being searched in the inheritance tree.
|
||||||
|
-- You can safely ignore it.
|
||||||
|
--
|
||||||
|
-- Please also note that the last universal ancestor of the classes (defined here in BaseClass) sets the default __tostring method
|
||||||
|
-- and __name attribute for nice class-name-printing. Unlike the previoulsy described attributes and methods however, it is done in a normal
|
||||||
|
-- inheritance-way and can be rewritten without any problem (rewritting __name is especially useful to easily identify your classes).
|
||||||
|
|
||||||
|
-- Lua versions compatibility
|
||||||
|
local unpack = table.unpack or unpack
|
||||||
|
|
||||||
|
--- All Lua 5.3 metamethods.
|
||||||
|
local metamethods = {
|
||||||
|
"__add", "__sub", "__mul", "__div", "__mod", "__pow", "__unm", "__idiv",
|
||||||
|
"__band", "__bor", "__bxor", "__bnot", "__shl", "__shr", "__tostring",
|
||||||
|
"__concat", "__len", "__eq", "__lt", "__le", "__index", "__newindex", "__call", "__gc"
|
||||||
|
}
|
||||||
|
|
||||||
|
local different --- When set, every class __index method will only return a value different from this one.
|
||||||
|
--- When using a proxied method, contains the last indexed class.
|
||||||
|
-- This is used for class.is(object); lastIndex will contain class so the is method can react accordingly, without having to be
|
||||||
|
-- re-set for each class (and therefore doesn't break the "different" mecanism).
|
||||||
|
local lastIndexed
|
||||||
|
local makeclass, methods, BaseClass
|
||||||
|
|
||||||
|
--- Classes defaults methods: will be re-set on each class creation.
|
||||||
|
-- If you overwrite them, you will only be able to call them from an object.
|
||||||
|
-- Methods starting with a "!" are "proxied methods": they're not present in the class table and will only be called through __index,
|
||||||
|
-- allowing more control over it (for example having access to lastIndexed).
|
||||||
|
methods = {
|
||||||
|
--- Create an object from the class.
|
||||||
|
-- In pratise, this only subclass the class and call the new method on it, so technically an object is a class.
|
||||||
|
-- Objects are exaclty like classes, but the __call metamethod will be replaced by one found in the parents,
|
||||||
|
-- or nil if doesn't exist (so an object is not directly subclassable).
|
||||||
|
-- (If no __call method is defined in a parent, you won't be able to call the object, but obj.__call will still
|
||||||
|
-- returns the default (subclassing) method, from one of the parents classes.)
|
||||||
|
-- The same happens with :new and :is, but since they're not metamethods, if not defined in a parent you won't
|
||||||
|
-- notice any difference.
|
||||||
|
-- TL;DR (since I think I'm not really clear): you can redefine __call, :new and :is in parents and use them in objects only.
|
||||||
|
-- A new object will only be created if calling the method "class:new(...)", if you call for example "class.new(someTable, ...)", it
|
||||||
|
-- will only execute the constructor defined in the class on someTable. This can be used to execute the parent constructor in a child
|
||||||
|
-- object, for example.
|
||||||
|
-- It should also be noted that if the new method returns non-nil value(s), they will be returned instead of the object.
|
||||||
|
["!new"] = function(self, ...)
|
||||||
|
if lastIndexed == self then
|
||||||
|
local obj, ret = self(), nil
|
||||||
|
-- Setting class methods to the ones found in parents (we use rawset in order to avoid calling the __newindex metamethod)
|
||||||
|
different = methods["!new"] rawset(obj, "new", obj:__index("new") or nil)
|
||||||
|
different = methods["!is"] rawset(obj, "is", obj:__index("is") or nil)
|
||||||
|
different = methods.__call rawset(obj, "__call", obj:__index("__call") or nil)
|
||||||
|
different = nil
|
||||||
|
-- Call constructor
|
||||||
|
if obj.new ~= methods["!new"] and type(obj.new) == "function" then ret = { obj:new(...) } end
|
||||||
|
if not ret or #ret == 0 then
|
||||||
|
return obj
|
||||||
|
else
|
||||||
|
return unpack(ret)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
different = methods["!new"]
|
||||||
|
local new = lastIndexed:__index("new") or nil
|
||||||
|
different = nil
|
||||||
|
return new(self, ...)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
--- Returns true if self is other or a subclass of other.
|
||||||
|
-- If other is nil, will return true if self is a subclass of the class who called this method.
|
||||||
|
-- Examples:
|
||||||
|
-- class.is(a) will return true if a is any class or object
|
||||||
|
-- (class()):is(class) will return true ((class()) is a subclass of class)
|
||||||
|
-- (class()).is(class) will return false (class isn't a subclass of (class()))
|
||||||
|
["!is"] = function(self, other)
|
||||||
|
if type(self) ~= "table" then return false end
|
||||||
|
if other == nil then other = lastIndexed end
|
||||||
|
if self == other then return true end
|
||||||
|
for _, t in ipairs(self.__super) do
|
||||||
|
if t == other then return true end
|
||||||
|
if t.is == methods["!is"] and t:is(other) then return true end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
--- Subclass the class: will create a class inheriting self and ... (... will have priority over self).
|
||||||
|
__call = function(self, ...)
|
||||||
|
local t = {...}
|
||||||
|
table.insert(t, self)
|
||||||
|
return makeclass(unpack(t))
|
||||||
|
end,
|
||||||
|
--- Internal value getting; this follows a precise search order.
|
||||||
|
-- For example: class(Base1, Base2){stuff}
|
||||||
|
-- When getting a value from the class, it will be first searched in stuff, then in Base1, then in all Base1 parents,
|
||||||
|
-- then in Base2, then in Base2 parents.
|
||||||
|
-- A way to describe this will be search in the latest added tables (from the farthest child to the first parents), from left-to-right.
|
||||||
|
-- self always refer to the initial table the metamethod was called on, super refers to the class currently being searched for a value.
|
||||||
|
__index = function(self, k, super)
|
||||||
|
local proxied = methods["!"..tostring(k)]
|
||||||
|
if proxied ~= nil and proxied ~= different then -- proxied methods
|
||||||
|
lastIndexed = self
|
||||||
|
return proxied
|
||||||
|
end
|
||||||
|
for _, t in ipairs((super or self).__super) do -- search in super (will follow __index metamethods)
|
||||||
|
local val = rawget(t, k)
|
||||||
|
if val ~= nil and val ~= different then return val end
|
||||||
|
-- Also covers the case when different search is enabled and the raw t[k] returns an identical value, so the __index metamethod search will be tried for another value.
|
||||||
|
if getmetatable(t) and getmetatable(t).__index then
|
||||||
|
val = getmetatable(t).__index(self, k, t)
|
||||||
|
if val ~= nil and val ~= different then return val end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Create a new class width parents ... (left-to-right priority).
|
||||||
|
function makeclass(...)
|
||||||
|
local class = {
|
||||||
|
__super = {} -- parent classes/tables list
|
||||||
|
}
|
||||||
|
for k, v in pairs(methods) do -- copy class methods
|
||||||
|
if k:sub(1, 1) ~= "!" then class[k] = v end -- except proxied methods
|
||||||
|
end
|
||||||
|
setmetatable(class, class)
|
||||||
|
for _, t in ipairs({...}) do -- fill super
|
||||||
|
if getmetatable(t) == nil then setmetatable(t, t) end -- auto-metatable the table
|
||||||
|
if type(t.__inherit) == "function" then t = t:__inherit(class) or t end -- call __inherit callback
|
||||||
|
table.insert(class.__super, t)
|
||||||
|
end
|
||||||
|
-- Metamethods query are always raw and thefore don't follow our __index, so we need to manually define thoses.
|
||||||
|
for _, metamethod in ipairs(metamethods) do
|
||||||
|
local inSuper = class:__index(metamethod)
|
||||||
|
if inSuper and rawget(class, metamethod) == nil then
|
||||||
|
rawset(class, metamethod, inSuper)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return class
|
||||||
|
end
|
||||||
|
|
||||||
|
--- The class which will be a parents for all the other classes.
|
||||||
|
-- We add some pretty-printing default in here. We temporarly remove the metatable in order to avoid a stack overflow.
|
||||||
|
BaseClass = makeclass {
|
||||||
|
__name = "class",
|
||||||
|
__tostring = function(self)
|
||||||
|
local mt, name = getmetatable(self), self.__name
|
||||||
|
setmetatable(self, nil)
|
||||||
|
local str = ("%s (%s)"):format(tostring(name), tostring(self))
|
||||||
|
setmetatable(self, mt)
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Class Commons implementation.
|
||||||
|
-- https://github.com/bartbes/Class-Commons
|
||||||
|
if common_class and not common then
|
||||||
|
common = {}
|
||||||
|
-- class = common.class(name, table, parents...)
|
||||||
|
function common.class(name, table, ...)
|
||||||
|
return BaseClass(table, ...){ __name = name, new = table.init }
|
||||||
|
end
|
||||||
|
-- instance = common.instance(class, ...)
|
||||||
|
function common.instance(class, ...)
|
||||||
|
return class:new(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return BaseClass
|
||||||
334
classtoi.lua
334
classtoi.lua
|
|
@ -1,185 +1,169 @@
|
||||||
--- Reuh's class library version 0.1.4. Lua 5.1-5.3 and LuaJit compatible.
|
--- classtoi v2: finding a sweet spot between classtoi-light and classtoi-heavy
|
||||||
-- Objects and classes behavior are identical, so you can consider this to be somewhat prototype-based.
|
-- aka getlost v2
|
||||||
-- Features:
|
|
||||||
-- * Multiple inheritance with class(parents...) or someclass(newstuff...)
|
|
||||||
-- * Every metamethods supported
|
|
||||||
-- * Everything in a class can be redefined (and will be usable in an object) (except __super)
|
|
||||||
-- * Preserve parents metamethods if already set
|
|
||||||
-- * Instanciate with class:new(...)
|
|
||||||
-- * Test inheritance relations with class/object.is(thing, isThis)
|
|
||||||
-- * Call object:new(...) on instanciation
|
|
||||||
-- * If object:new(...) returns non-nil values, they will be returned instead of the instance
|
|
||||||
-- * Call class.__inherit(class, inheritingClass) when creating a class inheriting the previous class. If class.__inherit returns a value, it will
|
|
||||||
-- be used as the parent table instead of class, allowing some pretty fancy behavior (it's like an inheritance metamethod).
|
|
||||||
-- * Implements Class Commons
|
|
||||||
-- * I don't like to do this, but you can redefine every field and metamethod after class creation (except __index and __super).
|
|
||||||
-- Not features / Things you may want to know:
|
|
||||||
-- * Will set the metatable of all parent classes/tables if no metatable is set (the table will be its own metatable).
|
|
||||||
-- * You can't redefine __super (any __super you define will be only avaible by searching in the default __super contents).
|
|
||||||
-- * Redefining __super or __index after class creation will break everything (though it should be ok with new, is, __call and everything else).
|
|
||||||
-- * When creating a new class, the methods new, is, __call, __index and __super will always be redefined, so trying to get theses fields
|
|
||||||
-- will return the default method and not the one you've defined. However, theses defaults will be replaced by yours automatically on instanciation,
|
|
||||||
-- except __super and __index, but __index should call your __index and act like you expect. __super will however always be the default one
|
|
||||||
-- and doesn't proxy in any way yours.
|
|
||||||
-- * __index metamethods will be called with an extra third argument, which is the current class being searched in the inheritance tree.
|
|
||||||
-- You can safely ignore it.
|
|
||||||
--
|
--
|
||||||
-- Please also note that the last universal ancestor of the classes (defined here in BaseClass) sets the default __tostring method
|
-- usage:
|
||||||
-- and __name attribute for nice class-name-printing. Unlike the previoulsy described attributes and methods however, it is done in a normal
|
--
|
||||||
-- inheritance-way and can be rewritten without any problem (rewritting __name is especially useful to easily identify your classes).
|
-- local class = require("class")
|
||||||
|
-- local Vehicle = class {
|
||||||
|
-- type = "vehicle", -- class name, optional
|
||||||
|
--
|
||||||
|
-- stability_threshold = 3, -- class variable, also availabe in instances
|
||||||
|
-- wheel_count = nil, -- doesn't do anything, but i like to keep track of variables that will need to be defined later in a subclass or a constructor
|
||||||
|
--
|
||||||
|
-- init = false, -- abstract class, can't be instanciated
|
||||||
|
--
|
||||||
|
-- is_stable = function(self) -- method, available both in class and instances
|
||||||
|
-- return self.wheel_count > self.stability_threshold
|
||||||
|
-- end
|
||||||
|
-- }
|
||||||
|
--
|
||||||
|
-- local Car = Vehicle { -- subclassing by calling the parent class; multiple inheritance possible by either chaining calls or passing several tables as arguments
|
||||||
|
-- type = "car",
|
||||||
|
-- wheel_count = 4,
|
||||||
|
-- color = nil,
|
||||||
|
-- init = function(self, color) -- constructor
|
||||||
|
-- self.color = color
|
||||||
|
-- end
|
||||||
|
-- }
|
||||||
|
-- local car = Car:new("red") -- instancing
|
||||||
|
-- print(car:is_stable(), car.color) -- true, "red"
|
||||||
|
--
|
||||||
|
-- the default class returned by require("class") contains a few other default methods that will be inherited by all subclasses
|
||||||
|
-- see line 99 and further for details & documentation
|
||||||
|
--
|
||||||
|
-- design philosophy:
|
||||||
|
-- do not add feature until we need it
|
||||||
|
-- what we want to be fast: instance creation, class & instance method call & property acces
|
||||||
|
-- do not care: class creation
|
||||||
|
--
|
||||||
|
-- and if you're wondering, no i'm not using either classtoi-heavy nor classtoi-light in any current project anymore.
|
||||||
|
|
||||||
-- Lua versions compatibility
|
--# helper functions #--
|
||||||
local unpack = table.unpack or unpack
|
|
||||||
|
|
||||||
--- All Lua 5.3 metamethods.
|
-- tostring that ignore __tostring methamethod
|
||||||
local metamethods = {
|
local function rawtostring(v)
|
||||||
"__add", "__sub", "__mul", "__div", "__mod", "__pow", "__unm", "__idiv",
|
local mt = getmetatable(v)
|
||||||
"__band", "__bor", "__bxor", "__bnot", "__shl", "__shr", "__tostring",
|
setmetatable(v, nil)
|
||||||
"__concat", "__len", "__eq", "__lt", "__le", "__index", "__newindex", "__call", "__gc"
|
local str = tostring(v)
|
||||||
}
|
setmetatable(v, mt)
|
||||||
|
|
||||||
local different --- When set, every class __index method will only return a value different from this one.
|
|
||||||
--- When using a proxied method, contains the last indexed class.
|
|
||||||
-- This is used for class.is(object); lastIndex will contain class so the is method can react accordingly, without having to be
|
|
||||||
-- re-set for each class (and therefore doesn't break the "different" mecanism).
|
|
||||||
local lastIndexed
|
|
||||||
local makeclass, methods, BaseClass
|
|
||||||
|
|
||||||
--- Classes defaults methods: will be re-set on each class creation.
|
|
||||||
-- If you overwrite them, you will only be able to call them from an object.
|
|
||||||
-- Methods starting with a "!" are "proxied methods": they're not present in the class table and will only be called through __index,
|
|
||||||
-- allowing more control over it (for example having access to lastIndexed).
|
|
||||||
methods = {
|
|
||||||
--- Create an object from the class.
|
|
||||||
-- In pratise, this only subclass the class and call the new method on it, so technically an object is a class.
|
|
||||||
-- Objects are exaclty like classes, but the __call metamethod will be replaced by one found in the parents,
|
|
||||||
-- or nil if doesn't exist (so an object is not directly subclassable).
|
|
||||||
-- (If no __call method is defined in a parent, you won't be able to call the object, but obj.__call will still
|
|
||||||
-- returns the default (subclassing) method, from one of the parents classes.)
|
|
||||||
-- The same happens with :new and :is, but since they're not metamethods, if not defined in a parent you won't
|
|
||||||
-- notice any difference.
|
|
||||||
-- TL;DR (since I think I'm not really clear): you can redefine __call, :new and :is in parents and use them in objects only.
|
|
||||||
-- A new object will only be created if calling the method "class:new(...)", if you call for example "class.new(someTable, ...)", it
|
|
||||||
-- will only execute the constructor defined in the class on someTable. This can be used to execute the parent constructor in a child
|
|
||||||
-- object, for example.
|
|
||||||
-- It should also be noted that if the new method returns non-nil value(s), they will be returned instead of the object.
|
|
||||||
["!new"] = function(self, ...)
|
|
||||||
if lastIndexed == self then
|
|
||||||
local obj, ret = self(), nil
|
|
||||||
-- Setting class methods to the ones found in parents (we use rawset in order to avoid calling the __newindex metamethod)
|
|
||||||
different = methods["!new"] rawset(obj, "new", obj:__index("new") or nil)
|
|
||||||
different = methods["!is"] rawset(obj, "is", obj:__index("is") or nil)
|
|
||||||
different = methods.__call rawset(obj, "__call", obj:__index("__call") or nil)
|
|
||||||
different = nil
|
|
||||||
-- Call constructor
|
|
||||||
if obj.new ~= methods["!new"] and type(obj.new) == "function" then ret = { obj:new(...) } end
|
|
||||||
if not ret or #ret == 0 then
|
|
||||||
return obj
|
|
||||||
else
|
|
||||||
return unpack(ret)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
different = methods["!new"]
|
|
||||||
local new = lastIndexed:__index("new") or nil
|
|
||||||
different = nil
|
|
||||||
return new(self, ...)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
--- Returns true if self is other or a subclass of other.
|
|
||||||
-- If other is nil, will return true if self is a subclass of the class who called this method.
|
|
||||||
-- Examples:
|
|
||||||
-- class.is(a) will return true if a is any class or object
|
|
||||||
-- (class()):is(class) will return true ((class()) is a subclass of class)
|
|
||||||
-- (class()).is(class) will return false (class isn't a subclass of (class()))
|
|
||||||
["!is"] = function(self, other)
|
|
||||||
if type(self) ~= "table" then return false end
|
|
||||||
if other == nil then other = lastIndexed end
|
|
||||||
if self == other then return true end
|
|
||||||
for _, t in ipairs(self.__super) do
|
|
||||||
if t == other then return true end
|
|
||||||
if t.is == methods["!is"] and t:is(other) then return true end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end,
|
|
||||||
--- Subclass the class: will create a class inheriting self and ... (... will have priority over self).
|
|
||||||
__call = function(self, ...)
|
|
||||||
local t = {...}
|
|
||||||
table.insert(t, self)
|
|
||||||
return makeclass(unpack(t))
|
|
||||||
end,
|
|
||||||
--- Internal value getting; this follows a precise search order.
|
|
||||||
-- For example: class(Base1, Base2){stuff}
|
|
||||||
-- When getting a value from the class, it will be first searched in stuff, then in Base1, then in all Base1 parents,
|
|
||||||
-- then in Base2, then in Base2 parents.
|
|
||||||
-- A way to describe this will be search in the latest added tables (from the farthest child to the first parents), from left-to-right.
|
|
||||||
-- self always refer to the initial table the metamethod was called on, super refers to the class currently being searched for a value.
|
|
||||||
__index = function(self, k, super)
|
|
||||||
local proxied = methods["!"..tostring(k)]
|
|
||||||
if proxied ~= nil and proxied ~= different then -- proxied methods
|
|
||||||
lastIndexed = self
|
|
||||||
return proxied
|
|
||||||
end
|
|
||||||
for _, t in ipairs((super or self).__super) do -- search in super (will follow __index metamethods)
|
|
||||||
local val = rawget(t, k)
|
|
||||||
if val ~= nil and val ~= different then return val end
|
|
||||||
-- Also covers the case when different search is enabled and the raw t[k] returns an identical value, so the __index metamethod search will be tried for another value.
|
|
||||||
if getmetatable(t) and getmetatable(t).__index then
|
|
||||||
val = getmetatable(t).__index(self, k, t)
|
|
||||||
if val ~= nil and val ~= different then return val end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
--- Create a new class width parents ... (left-to-right priority).
|
|
||||||
function makeclass(...)
|
|
||||||
local class = {
|
|
||||||
__super = {} -- parent classes/tables list
|
|
||||||
}
|
|
||||||
for k, v in pairs(methods) do -- copy class methods
|
|
||||||
if k:sub(1, 1) ~= "!" then class[k] = v end -- except proxied methods
|
|
||||||
end
|
|
||||||
setmetatable(class, class)
|
|
||||||
for _, t in ipairs({...}) do -- fill super
|
|
||||||
if getmetatable(t) == nil then setmetatable(t, t) end -- auto-metatable the table
|
|
||||||
if type(t.__inherit) == "function" then t = t:__inherit(class) or t end -- call __inherit callback
|
|
||||||
table.insert(class.__super, t)
|
|
||||||
end
|
|
||||||
-- Metamethods query are always raw and thefore don't follow our __index, so we need to manually define thoses.
|
|
||||||
for _, metamethod in ipairs(metamethods) do
|
|
||||||
local inSuper = class:__index(metamethod)
|
|
||||||
if inSuper and rawget(class, metamethod) == nil then
|
|
||||||
rawset(class, metamethod, inSuper)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return class
|
|
||||||
end
|
|
||||||
|
|
||||||
--- The class which will be a parents for all the other classes.
|
|
||||||
-- We add some pretty-printing default in here. We temporarly remove the metatable in order to avoid a stack overflow.
|
|
||||||
BaseClass = makeclass {
|
|
||||||
__name = "class",
|
|
||||||
__tostring = function(self)
|
|
||||||
local mt, name = getmetatable(self), self.__name
|
|
||||||
setmetatable(self, nil)
|
|
||||||
local str = ("%s (%s)"):format(tostring(name), tostring(self))
|
|
||||||
setmetatable(self, mt)
|
|
||||||
return str
|
return str
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- deep table copy, preserve metatable
|
||||||
|
local function copy(t, cache)
|
||||||
|
if cache == nil then cache = {} end
|
||||||
|
if cache[t] then return cache[t] end
|
||||||
|
local r = {}
|
||||||
|
cache[t] = r
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
r[k] = type(v) == "table" and copy(v, cache) or v
|
||||||
|
end
|
||||||
|
return setmetatable(r, getmetatable(t))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add val to set
|
||||||
|
local function add_to_set(set, val)
|
||||||
|
if not set[val] then
|
||||||
|
table.insert(set, val)
|
||||||
|
set[val] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--# class creation logic #--
|
||||||
|
local new_class, class_mt
|
||||||
|
|
||||||
|
new_class = function(...)
|
||||||
|
local class = {}
|
||||||
|
local include = {...}
|
||||||
|
for i=1, #include do
|
||||||
|
local parent = include[i]
|
||||||
|
parent = parent.__included ~= nil and parent:__included(class) or parent
|
||||||
|
for k, v in pairs(parent) do
|
||||||
|
class[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
class.__index = class
|
||||||
|
setmetatable(class, class_mt)
|
||||||
|
return class.__created ~= nil and class:__created() or class
|
||||||
|
end
|
||||||
|
|
||||||
|
class_mt = {
|
||||||
|
__call = new_class,
|
||||||
|
__tostring = function(self)
|
||||||
|
local name = self.type and ("class %q"):format(self.type) or "class"
|
||||||
|
return rawtostring(self):gsub("^table", name)
|
||||||
|
end
|
||||||
}
|
}
|
||||||
|
class_mt.__index = class_mt
|
||||||
|
|
||||||
--- Class Commons implementation.
|
--# base class and its contents #--
|
||||||
-- https://github.com/bartbes/Class-Commons
|
-- feel free to redefine these as needed in your own classes; all of these are also optional and can be deleted.
|
||||||
if common_class and not common then
|
return new_class {
|
||||||
common = {}
|
--- instanciate. arguments are passed to the (eventual) constructor :init.
|
||||||
-- class = common.class(name, table, parents...)
|
-- behavior undefined when called on an object.
|
||||||
function common.class(name, table, ...)
|
-- set to false to make class non-instanciable (will give unhelpful error on instanciation attempt).
|
||||||
return BaseClass(table, ...){ __name = name, new = table.init }
|
-- obj = class:new(...)
|
||||||
|
new = function(self, ...)
|
||||||
|
local obj = setmetatable({}, self)
|
||||||
|
return obj.init ~= nil and obj:init(...) or obj
|
||||||
|
end,
|
||||||
|
--- constructor. arguments are passed from :new. if :init returns a value, it will be returned by :new instead of the self object.
|
||||||
|
-- set to false to make class abstract (will give unhelpful error on instanciation attempt), redefine in subclass to make non-abstract again.
|
||||||
|
-- init = function(self, ...) content... end
|
||||||
|
init = nil,
|
||||||
|
--- check if the object is an instance of this class.
|
||||||
|
-- class:is(obj)
|
||||||
|
-- obj:is(class)
|
||||||
|
is = function(self, other) -- class:is(obj)
|
||||||
|
if getmetatable(self) == class_mt then
|
||||||
|
return getmetatable(other) == self
|
||||||
|
else
|
||||||
|
return other:is(self)
|
||||||
end
|
end
|
||||||
-- instance = common.instance(class, ...)
|
end,
|
||||||
function common.instance(class, ...)
|
--- check if the object is an instance of this class or of a class that inherited this class.
|
||||||
return class:new(...)
|
-- parentclass:issub(obj)
|
||||||
|
-- parentclass:issub(class)
|
||||||
|
-- obj:issub(parentclass)
|
||||||
|
issub = function(self, other)
|
||||||
|
if getmetatable(self) == class_mt then
|
||||||
|
return other.__parents and other.__parents[self] or self:is(other)
|
||||||
|
else
|
||||||
|
return other:issub(self)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
--- check if self is a class
|
||||||
|
-- class:isclass()
|
||||||
|
isclass = function(self)
|
||||||
|
return getmetatable(self) == class_mt
|
||||||
|
end,
|
||||||
|
--- called when included in a new class. if it returns a value, it will be used as the included table instead of the self table.
|
||||||
|
-- default function tracks parent classes and is needed for :issub to work, and returns a deep copy of the included table.
|
||||||
|
__included = function(self, into)
|
||||||
|
-- add to parents
|
||||||
|
if not into.__parents then
|
||||||
|
into.__parents = {}
|
||||||
|
end
|
||||||
|
local __parents = self.__parents
|
||||||
|
if __parents then
|
||||||
|
for i=1, #__parents do
|
||||||
|
add_to_set(into.__parents, __parents[i])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
add_to_set(into.__parents, self)
|
||||||
return BaseClass
|
-- create copied table
|
||||||
|
local copied = copy(self)
|
||||||
|
copied.__parents = nil -- prevent __parents being overwritten
|
||||||
|
return copied
|
||||||
|
end,
|
||||||
|
-- automatically created by __included and needed for :issub to work
|
||||||
|
-- list and set of classes that are parents of this class: { parent_a, [parent_a] = true, parent_b, [parent_b] = true, ... }
|
||||||
|
__parents = nil,
|
||||||
|
--- called on the class when it is created. if it returns a value, it will be returned as the new class instead of the self class.
|
||||||
|
__created = nil,
|
||||||
|
--- pretty printing. type is used as the name of the class.
|
||||||
|
type = "object",
|
||||||
|
__tostring = function(self)
|
||||||
|
return rawtostring(self):gsub("^table", self.type)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,30 @@
|
||||||
local class = dofile(arg[1] or "../classtoi.lua")
|
-- can be used to compare different versions: lua performance/performance.lua classtoi-light.lua classtoi.lua classtoi-heavy.lua
|
||||||
|
|
||||||
local function time(title, f)
|
-- load libs to test
|
||||||
|
if not arg[1] then arg[1] = "../classtoi.lua" end
|
||||||
|
|
||||||
|
local totest = {}
|
||||||
|
local referencesource = arg[1]
|
||||||
|
for i=1, #arg do
|
||||||
|
totest[arg[i]] = dofile(arg[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- setup results
|
||||||
|
local results = {}
|
||||||
|
local function time(source, title, f)
|
||||||
collectgarbage()
|
collectgarbage()
|
||||||
local start = os.clock()
|
local start = os.clock()
|
||||||
for i=0, 5e4 do f() end
|
for i=0, 5e4 do f() end
|
||||||
print(title, os.clock() - start)
|
local result = os.clock() - start
|
||||||
|
|
||||||
|
if not results[title] then results[title] = {} end
|
||||||
|
results[title][source] = result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- perform benchmark
|
||||||
|
for source, class in pairs(totest) do
|
||||||
do
|
do
|
||||||
time("class creation", function()
|
time(source, "class creation", function()
|
||||||
local A = class()
|
local A = class()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
@ -16,7 +32,7 @@ end
|
||||||
do
|
do
|
||||||
local A = class()
|
local A = class()
|
||||||
|
|
||||||
time("instance creation", function()
|
time(source, "instance creation", function()
|
||||||
local a = A:new()
|
local a = A:new()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
@ -30,11 +46,11 @@ do
|
||||||
|
|
||||||
local a = A:new()
|
local a = A:new()
|
||||||
|
|
||||||
time("instance method invocation", function()
|
time(source, "instance method invocation", function()
|
||||||
a:foo()
|
a:foo()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
time("class method invocation", function()
|
time(source, "class method invocation", function()
|
||||||
A:foo()
|
A:foo()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
@ -50,11 +66,23 @@ do
|
||||||
|
|
||||||
local b = B:new()
|
local b = B:new()
|
||||||
|
|
||||||
time("inherited instance method invocation", function()
|
time(source, "inherited instance method invocation", function()
|
||||||
b:foo()
|
b:foo()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
time("inherited class method invocation", function()
|
time(source, "inherited class method invocation", function()
|
||||||
B:foo()
|
B:foo()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- display results
|
||||||
|
for test, sources in pairs(results) do
|
||||||
|
print(test..":")
|
||||||
|
for i=1, #arg do
|
||||||
|
local source = arg[i]
|
||||||
|
local result = sources[source]
|
||||||
|
local ratio = math.floor(result/sources[referencesource]*1000)/1000
|
||||||
|
print(("\t%s: %ss (x%s)"):format(source, result, ratio))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
|
-- note: this will test again the classtoi-heavy featureset only
|
||||||
|
|
||||||
local T = require("knife-test")
|
local T = require("knife-test")
|
||||||
|
|
||||||
-- luacheck: ignore T
|
-- luacheck: ignore T
|
||||||
T("Given the base class", function(T)
|
T("Given the base class", function(T)
|
||||||
local class = dofile(arg[1] or "../classtoi.lua")
|
local class = dofile(arg[1] or "../classtoi-heavy.lua")
|
||||||
|
|
||||||
-- Inheritance
|
-- Inheritance
|
||||||
T("When subclassed with an attribute", function(T)
|
T("When subclassed with an attribute", function(T)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue