Skip to content

Instantly share code, notes, and snippets.

@Orochimarufan
Created February 11, 2014 18:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Orochimarufan/8941357 to your computer and use it in GitHub Desktop.
Save Orochimarufan/8941357 to your computer and use it in GitHub Desktop.
Lua OOP library (ccobjects)
-- (c) 2012 Orochimarufan
-- Pythonic OOP for Lua
-- make table callable.
local table = setmetatable({}, {__index=table, __call=function(...)return{...}end})
-- strings
local err = {
NOATTR = "%s has no attribute '%s'",
NOCALL = "%s is not callable.",
NOMETH = "Cannot find method %s() on %s",
ARG_NOTNIL = "%s() argument #%i: cannot be NIL",
ARG_OBJECT = "%s() argument #%i: must be an Object",
ARG_TYPE = "%s() argument #%i: must be %s",
}
-- Declarations
local META = table()
local MetaType = table()
Type = table()
Object = table()
-- Is* Helpers
local function IsObject(o)
return type(o)=="table" and getmetatable(o) == META
end
local function objstring(obj)
local id = rawget(obj, "__id__")
local cls = rawget(obj, "__class__")
if not cls then
return ("<? Object at %s>"):format(id)
end
local name = rawget(cls, "__name__")
return ("<'%s' Object at %s>"):format(name, id)
end
-- Hardcoded Attribute Lookups
local function lookup_type(cls, name)
--if cls then print(objstring(cls), name) end
local res
while (cls~=nil and res==nil) do
res = rawget(cls, "__dict__")[name]
cls = rawget(cls, "__base__")
end
return res
end
local function lookup_object(obj, name)
local res = rawget(obj, name)
if (res ~= nil) then
return res
else
local cls = rawget(obj, "__class__")
return lookup_type(cls, name)
end
end
local function lookup_method(obj, name)
local cls = rawget(obj, "__class__")
return lookup_type(cls, name)
end
-- get the "table: 0xNNNNNNN" ID of a table
local function id(t)
local mt = getmetatable(t)
if (mt ~= nil and mt.__tostring ~= nil) then
setmetatable(t, nil)
end
local id = tostring(t):sub(8)
if (mt ~= nil and mt.__tostring ~= nil) then
setmetatable(t, mt)
end
return id
end
-- export for use in submodules
_util = {
isobject = IsObject,
objstring = objstring,
lookup_type = lookup_type,
lookup_object = lookup_object,
lookup_method = lookup_method,
id = id,
table = table,
err = err
}
-----------------------------------------------------------------------
-- Metatable
-----------------------------------------------------------------------
-- Metamethods
function META:__index(name)
--print(("get %s [%s]"):format(tostring(self), name))
local f = lookup_method(self, "__getattr__")
if f ~= nil then
return f(self, name)
end
error(err.NOATTR:format(self, name), 2)
end
function META:__newindex(name, value)
--print(("set %s [%s] = %s"):format(tostring(self), tostring(name), tostring(value)))
local f = lookup_method(self, "__setattr__")
if f ~= nil then
return f(self, name, value)
end
error(err.NOATTR:format(self, name),2)
end
function META:__call(...)
local f = lookup_method(self, "__call__")
if f ~= nil then
return f(self, ...)
end
error(err.NOCALL:format(self), 2)
end
function META:__tostring()
local f --= lookup_method(self, "__str__")
if f ~= nil then
return f(self)
end
return objstring(self)
end
-----------------------------------------------------------------------
-- Stock Object Construction
-----------------------------------------------------------------------
Object.__class__ = Type
Object.__name__ = "Object"
Object.__base__ = nil
Object.__dict__ = table()
Object.__id__ = id(Object)
setmetatable(Object, META)
MetaType.__base__ = Type
MetaType.__class__ = {__name__="MetaTypeType (intern)",__base__=Type,__class__=nil,__id__=nil, __dict__=table()}
MetaType.__name__ = "MetaType (intern)"
MetaType.__id__ = id(MetaType)
MetaType.__dict__ = table()
setmetatable(MetaType, META)
Type.__base__ = Object
Type.__class__ = MetaType
Type.__name__ = "Type"
Type.__dict__ = table()
Type.__id__ = id(Type)
setmetatable(Type, META)
function Type.__dict__.__setattr__(cls, name, value)
rawget(cls, "__dict__")[name] = value
end
-----------------------------------------------------------------------
-- Type Type
-----------------------------------------------------------------------
function MetaType.__dict__:__getattr__(name)
return lookup_type(self, name)
end
function MetaType.__dict__:__str__()
return ("<metaclass %s>"):format(self.__name__)
end
function MetaType.__dict__:__call__(...)
local args = {...}
if (self == Type and #args == 1) then
local ptype = type(args[1])
if (ptype ~= "table") then
return ptype
elseif (IsObject(args[1])) then
return rawget(args[1], "__class__")
else
return ptype
end
end
return Type.__dict__.__call__(self, unpack(args))
end
-----------------------------------------------------------------------
-- Type (The type of a class)
-----------------------------------------------------------------------
-- type attribute access
function Type.__dict__.__getattr__(cls, name)
-- look in the Metaclass
local val = lookup_method(cls, name)
if (val ~= nil) then
return val
end
-- look in self
val = lookup_type(cls, name)
if (val ~= nil) then
return val
end
error(err.NOATTR:format(self, name), 3)
end
-- tostring
function Type.__str__(cls)
return ("<class %s>"):format(cls.__name__)
end
-- Instantiation
function Type.__call__(cls, ...)
local f = lookup_type(cls, "__new__", true)
if f == nil then
error(err.NOMETH:format("__new__", cls), 3) end
local self = f(cls, ...)
f = lookup_type(cls, "__init__")
if f == nil then
error(err.NOMETH:format("__init__", cls), 3) end
f(self, ...)
return self
end
-- Class Creation
function Type:__new__(name, base, dict)
assert(type(name)=="string", err.ARG_TYPE:format(self, 1, "string"))
if base == nil then base = Object end
if dics == nil then dict = table() end
if not IsObject(base) then error(err.ARG_OBJECT:format(self.__name__, 2), 3) end
if not type(dict)=="table" then error(err.ARG_TYPE:format(self.__name__, 3), 3) end
if not issubclass(self, rawget(base, "__class__")) then
error("Metaclass must be subclass of base metaclass", 3) end
local cls = table()
cls.__name__ = name
cls.__base__ = base
cls.__dict__ = dict
cls.__class__ = self
cls.__id__ = id(cls)
setmetatable(cls, META)
return cls
end
function Type:subclass(name, dict)
return self.__class__(name, self, dict)
end
function Type:subclass2(dict)
local name = dict.__name__
if not name then
error("subclass2 requires __name__ to be in dict!",2)
end
dict.__name__ = nil
return self.__class__(name, self, dict)
end
-----------------------------------------------------------------------
-- Object (the type of an instance)
-----------------------------------------------------------------------
-- Allocator
function Object.__new__(cls)
local self = table()
self.__class__ = cls
self.__id__ = id(self)
setmetatable(self, META)
return self
end
-- Constructor/Initializer
function Object:__init__()
-- Object needs no Initialization
end
-- getattr
function Object:__getattr__(name)
-- only called if not in own table!
-- look in bases
local val = lookup_method(self)
if val ~= nil then
return val
end
error(err.NOATTR:format(self, name), 3)
end
-- setattr
function Object:__setattr__(name, value)
rawset(self, name, value)
end
-- inheritance checks
function Object:__instancecheck__(instance)
return issubclass(Type(instance), self)
end
function Object:__subclasscheck__(subclass)
local this = subclass
while this do
if this == self then return true end
this = rawget(this, "__base__")
end
return false
end
-----------------------------------------------------------------------
-- Public Functions
-----------------------------------------------------------------------
function isinstance(inst, cls)
if not IsObject(inst) then error(err.ARG_OBJECT:format("isinstance", 1), 2) end
if not IsObject(cls) then error(err.ARG_OBJECT:format("isinstance", 2), 2) end
local f = lookup_type(cls, "__instancecheck__")
return f(cls, inst)
end
function issubclass(subclass, cls)
if not IsObject(subclass) then error(err.ARG_OBJECT:format("issubclass", 1), 2) end
if not IsObject(cls) then error(err.ARG_OBJECT:format("issubclass", 2), 2) end
local f = lookup_type(cls, "__subclasscheck__")
return f(cls, subclass)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment