1
0
Fork 0
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:
Étienne Fildadut 2023-12-22 13:37:09 +01:00
parent 3bb90f9034
commit f43867739b
6 changed files with 432 additions and 224 deletions

View file

@ -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.

View file

@ -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
View 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

View file

@ -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
}
--- Class Commons implementation. -- deep table copy, preserve metatable
-- https://github.com/bartbes/Class-Commons local function copy(t, cache)
if common_class and not common then if cache == nil then cache = {} end
common = {} if cache[t] then return cache[t] end
-- class = common.class(name, table, parents...) local r = {}
function common.class(name, table, ...) cache[t] = r
return BaseClass(table, ...){ __name = name, new = table.init } for k, v in pairs(t) do
r[k] = type(v) == "table" and copy(v, cache) or v
end end
-- instance = common.instance(class, ...) return setmetatable(r, getmetatable(t))
function common.instance(class, ...) end
return class:new(...)
-- 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
end end
return BaseClass --# 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
--# base class and its contents #--
-- feel free to redefine these as needed in your own classes; all of these are also optional and can be deleted.
return new_class {
--- instanciate. arguments are passed to the (eventual) constructor :init.
-- behavior undefined when called on an object.
-- set to false to make class non-instanciable (will give unhelpful error on instanciation attempt).
-- 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,
--- check if the object is an instance of this class or of a class that inherited this class.
-- 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
add_to_set(into.__parents, self)
-- 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
}

View file

@ -1,27 +1,43 @@
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
do -- perform benchmark
time("class creation", function() for source, class in pairs(totest) do
do
time(source, "class creation", function()
local A = class() local A = class()
end) end)
end 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
do do
local A = class { local A = class {
foo = function(self) foo = function(self)
return 1 return 1
@ -30,16 +46,16 @@ 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
do do
local A = class { local A = class {
foo = function(self) foo = function(self)
return 1 return 1
@ -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
-- 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 end

View file

@ -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)