Simple Educative Class System

Also known as SECS (pronounce as 'sex'). This is a simple implementation of classes that works, it's not commented, so it's a nice test of your lua skills.

Remember to check here once in a while, it might have updated.

Basic version

--[[
Copyright (c) 2009 Bart van Strien

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
]]


__HAS_SECS_COMPATIBLE_CLASSES__ = true

local class_mt = {}

function class_mt:__index(key)
    return self.__baseclass[key]
end

class = setmetatable({ __baseclass = {} }, class_mt)

function class:new(...)
    local c = {}
    c.__baseclass = self
    setmetatable(c, getmetatable(self))
    if c.init then
        c:init(...)
    end
    return c
end

Example

require "class" --this assumes you've saved the code above in class.lua

myclass = class:new()
myclass.value = 13
function myclass:setvalue(v)
    self.value = v
end
object = myclass:new()
object:setvalue(128)
--myclass.value is still 13 and object.value is now 128
anotherObject = object:new()
--anotherObject.value is 128

Class Commons enabled basic version

--[[
Copyright (c) 2009-2011 Bart van Strien

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
]]


local class_mt = {}

function class_mt:__index(key)
    return self.__baseclass[key]
end

class = setmetatable({ __baseclass = {} }, class_mt)

function class:new(...)
    local c = {}
    c.__baseclass = self
    setmetatable(c, getmetatable(self))
    if c.init then
        c:init(...)
    end
    return c
end

if class_commons ~= false then --on by default
    common = {}

    function common.class(name, t, parent)
        parent = parent or class
        t.__baseclass = parent
        return setmetatable(t, getmetatable(parent))
    end

    function common.instance(class, ...)
        return class:new(...)
    end
end
O.png It's likely versions below this are bugged. Sorry about that!  

Advanced version

--[[
Copyright (c) 2009 Bart van Strien

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
]]


__HAS_SECS_COMPATIBLE_CLASSES__ = true

local class_mt = {}

function class_mt:__index(key)
    if rawget(self, "__baseclass") then
        return self.__baseclass[key]
    end
    return nil
end

class = setmetatable({ __baseclass = {} }, class_mt)

function class:new(...)
    local c = {}
    c.__baseclass = self
    setmetatable(c, getmetatable(self))
    if c.init then
        c:init(...)
    end
    return c
end

function class:convert(t)
    t.__baseclass = self
    setmetatable(t, getmetatable(self))
    return t
end

function class:addparent(...)
    if not rawget(self.__baseclass, "__isparenttable") then
        local t = {__isparenttable = true, self.__baseclass, ...}
        local mt = {}
        function mt:__index(key)
            for i, v in pairs(self) do
                if i ~= "__isparenttable" and v[key] then
                    return v[key]
                end
            end
            return nil
        end
        self.__baseclass = setmetatable(t, mt)
    else
        for i, v in ipairs{...} do
            table.insert(self.__baseclass, v)
        end
    end
    return self
end

function class:setmetamethod(name, value)
    local mt = getmetatable(self)
    local newmt = {}
    for i, v in pairs(mt) do
        newmt[i] = v
    end
    newmt[name] = value
    setmetatable(self, newmt)
end

Example

--NOTE: this example only contains features the basic version doesn't, if you want to see those functions see the basic example
--require it here

--create the tables/classes
cl1 = class:new()
cl2 = class:new()
cl3 = {}

--fill the tables/classes
cl1.val1 = 12
cl2.val2 = 24
cl3.val3 = 48

--create the new merged class
merged = cl1:new()
print(merged.val1, merged.val2, merged.val3) --> 12  nil  nil
merged:addparent(cl2)
print(merged.val1, merged.val2, merged.val3) --> 12  24  nil
--remember cl3 is a normal table, not a class?
class:convert(cl3) --converts the table in place and returns it, so you can use it in expressions
merged:addparent(cl3) --could also be merged:addparent(class:convert(cl3))
print(merged.val1, merged.val2, merged.val3) --> 12  24  48

--convert is part of any class, the parent of the converted class is the class where convert is called from
--NOTE: addparent supports multiple parents at once, the way that it's done here is to serve as an example

Full version (AKA overcomplicated)

--[[
Copyright (c) 2009 Bart van Strien

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
]]


__HAS_SECS_COMPATIBLE_CLASSES__ = true

local class_mt = {}

function class_mt:__index(key)
    if rawget(self, "__baseclass") then
        return self.__baseclass[key]
    end
    return nil
end

function class_mt:__call(...)
    return self:new(...)
end

function class_mt:__add(parent)
    return self:addparent(parent)
end

function class_mt:__eq(other)
    if not other.__baseclass or other.__baseclass ~= self.__baseclass then return false end
    for i, v in pairs(self) do
        if not other[i] then
            return false
        end
    end
    for i, v in pairs(other) do
        if not self[i] then
            return false
        end
    end
    return true
end

function class_mt:__lt(other)
    if not other.__baseclass then return false end
    if rawget(other.__baseclass, "__isparenttable") then
        for i, v in pairs(other.__baseclass) do
            if self == v or getmetatable(self).__lt(self, v) then return true end
        end
    else
        if self == other.__baseclass or getmetatable(self).__lt(self, other.__baseclass) then return true end
    end
    return false
end

function class_mt:__le(other)
    return (self < other or self == other)
end

local pt_mt = {}
function pt_mt:__index(key)
    for i, v in pairs(self) do
        if i ~= "__isparenttable" and v[key] then
            return v[key]
        end
    end
    return nil
end

class = setmetatable({ __baseclass = {} }, class_mt)

function class:new(...)
    local c = {}
    c.__baseclass = self
    setmetatable(c, getmetatable(self))
    if c.init then
        c:init(...)
    end
    return c
end

function class:convert(t)
    t.__baseclass = self
    setmetatable(t, getmetatable(self))
    return t
end

function class:addparent(...)
    if not rawget(self.__baseclass, "__isparenttable") then
        local t = {__isparenttable = true, self.__baseclass, ...}
        self.__baseclass = setmetatable(t, pt_mt)
    else
        for i, v in ipairs{...} do
            table.insert(self.__baseclass, v)
        end
    end
    return self
end

function class:setmetamethod(name, value)
    local mt = getmetatable(self)
    local newmt = {}
    for i, v in pairs(mt) do
        newmt[i] = v
    end
    newmt[name] = value
    setmetatable(self, newmt)
end


Example

--NOTE: This example only contains features the advanced version doesn't have
--require it here

cl1 = class() --new is implied
cl2 = class()

cl1.val1 = 12
cl2.val2 = 24

merged = cl1() + cl2 --yes, we can just add cl2 as if we were doing math
print(merged.val1, merged.val2) --> 12  24

if merged > cl1 then
    print("Merged is a derivative of cl1") --which it is, so this is printed
end
if merged > cl2 then
    print("Merged is a derivative of cl2") --it's that as well, so print
end

merged2 = cl1() + cl2  --create another derivative with the same parents
merged2.val1 = 36  --we change one value

if merged == merged2 then
    print("Merged2 is of the same type as merged") --they are the same type, note this only works when changing, when you add or remove values it is considered as a new class
end

Revised version

Warning, this might update more than the others.

--[[
Copyright (c) 2010 Bart van Strien

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
]]


__HAS_SECS_COMPATIBLE_CLASSES__ = true

local class_mt = {}

function class_mt:__index(key)
    if key == "__private" or (rawget(self, "__private") and self.__private[key]) then
        return nil
    end
    if rawget(self, "__baseclass") then
        return self.__baseclass[key]
    end
    return nil
end

function class_mt:__call(...)
    return self:new(...)
end

function class_mt:__add(parent)
    return self:addparent(parent)
end

function class_mt:__eq(other)
    if not other.__baseclass or other.__baseclass ~= self.__baseclass then return false end
    for i, v in pairs(self) do
        if not other[i] then
            return false
        end
    end
    for i, v in pairs(other) do
        if not self[i] then
            return false
        end
    end
    return true
end

function class_mt:__lt(other)
    if not other.__baseclass then return false end
    if rawget(other.__baseclass, "__isparenttable") then
        for i, v in pairs(other.__baseclass) do
            if self == v or getmetatable(self).__lt(self, v) then return true end
        end
    else
        if self == other.__baseclass or getmetatable(self).__lt(self, other.__baseclass) then return true end
    end
    return false
end

function class_mt:__le(other)
    return (self < other or self == other)
end

local pt_mt = {}
function pt_mt:__index(key)
    for i, v in pairs(self) do
        if i ~= "__isparenttable" and v[key] then
            return v[key]
        end
    end
    return nil
end

class = setmetatable({ __baseclass = {} }, class_mt)

function class:new(...)
    local c = {}
    c.__baseclass = self
    setmetatable(c, getmetatable(self))
    c:__init(...)
    return c
end

function class:__init(...)
    local args = {...}
    if rawget(self, "init") then
        args = {self:init(...) or ...}
    end
    if self.__baseclass.__init then
        self.__baseclass:__init(unpack(args))
    end
end

function class:convert(t)
    t.__baseclass = self
    setmetatable(t, getmetatable(self))
    return t
end

function class:addparent(...)
    if not rawget(self.__baseclass, "__isparenttable") then
        local t = {__isparenttable = true, self.__baseclass, ...}
        self.__baseclass = setmetatable(t, pt_mt)
    else
        for i, v in ipairs{...} do
            table.insert(self.__baseclass, v)
        end
    end
    return self
end

function class:setmetamethod(name, value)
    local mt = getmetatable(self)
    local newmt = {}
    for i, v in pairs(mt) do
        newmt[i] = v
    end
    newmt[name] = value
    setmetatable(self, newmt)
end

Example

Same as full.

See also

Bartbes, creator and maintainer of SECS.

Personal tools