Klass Module

lua-users home
wiki

Difference (from prior major revision) (no other diffs)

Changed: 1c1
Another implementation of classes. Not the fastest one, but takes only about 2k (without comments), introduces "inherited(self, ...)" calls, has InstanceOf()/AncestorOf() methods and constructors. Destructors are also implemented, via unofficial/unsupported newproxy(). Pure Lua.
Another implementation of classes. Works with Lua 5.1 and Lua 5.2. Not the fastest one, but takes only about 2k (without comments), introduces "inherited(self, ...)" calls, has isA() method, and constructors. Destructors are also implemented, via unofficial/unsupported newproxy() trick or with "__gc" metamethod.

Changed: 5c5
-- $VERSION 0.0.2
-- $VERSION 0.0.5

Changed: 8c8
-- another one simple class engine %-)
-- another simple prototype-based class engine with finalizers and support for 'inherited' calls

Changed: 11c11
-- public domain
-- WTFPL

Added: 13a14
-- kmodule

Changed: 28c29
-- `Destructor' will be called when object is going to be destroyed by GC
-- `Destructor' will be called when class is going to be destroyed by GC

Changed: 32,35c33,35
-- you can alter methods even in class instance!
-- this means that you can extend already defined classes and instantiated
-- objects. all class extensions are immediate visible to all instantiated
-- objects.
-- earch method can know it's name from variable (not field!) method
--
-- one can add/alter methods in class or instance in runtime.

Changed: 38,39c38,39
-- [+] added :InstanceOf?(class) method
-- [+] added :AncestorOf?(class) method (true for c:AncestorOf?(c)!)
-- [+] works with Lua 5.2 beta
-- [+] added :isA(obj)

Changed: 41,42c41,92
-- $WARNING
-- this isn't a real module, but you can require() it! %-)

-- cache global functions
local assert, type, rawget, rawset = assert, type, rawget, rawset;
local getmetatable, setmetatable = getmetatable, setmetatable;
--local newproxy = newproxy;
local setfenv, getfenv = setfenv, getfenv;
local getinfo, getupvalue, setupvalue, upvaluejoin;
local newlua = false;
local _klassG;

--local print, tostring = print, tostring;


if not setfenv then
require "debug";
getinfo, getupvalue, setupvalue, upvaluejoin = debug.getinfo, debug.getupvalue, debug.setupvalue, debug.upvaluejoin;
newlua = true;

setfenv = function (f, t)
f = (type(f) == 'function' and f or getinfo(f+1, 'f').func);
local up, name = 0;
repeat
up = up+1;
name = getupvalue(f, up)
until name == '_ENV' or name == nil;
if name then
upvaluejoin(f, up, function() return name; end, 1); -- use unique upvalue
setupvalue(f, up, t);
end;
end;

getfenv = function (f)
f = (type(f) == 'function' and f or getinfo(f+1, 'f').func);
local up, name, val = 0;
repeat
up = up+1;
name, val = getupvalue(f, up);
until name == '_ENV' or name == nil;
return val;
end;

_klassG = _ENV;
end;


module(..., package.seeall);


local specialFields = {
_className=true,
_parent=true,
};

Changed: 46c96
-- (method) find field in class of in parents
-- find field in class or in it's parents

Changed: 57,60c107,116
local function FindField? (class, fld)
local res = rawget(class, fld); if res ~= nil then return res; end;
local pl = rawget(class, "parent"); if pl == nil then return nil; end;
return pl[fld];
local function findField (class, fld)
if specialFields[fld] then return rawget(class, fld); end;
local res;
repeat
--print("findField", fld, class._className);
res = rawget(rawget(class, "_fields"), fld); if res ~= nil then return res; end;
-- not found, go up
class = rawget(class, "_parent");
until not class;
-- default result: nil

Changed: 64c120,121
local seeallmt = { __index = _G };
-- metatable for 'see everything'
--local seeallmt = { __index = _G };

Changed: 67c124
-- (method) find field in class of in parents
-- add field or method, install environment for method

Added: 72a130,131
-- value
-- field value or metod

Changed: 74,75c133,134
local function NewField? (class, fld, value)
if value == nil then return; end;
local function newField (class, fld, value)
local fields = rawget(class, "_fields");

Changed: 77,78c136,137
-- new method, change envtable
-- this trick allows use of "inherited(self, ...)"
-- new method, change envtable to make inherited() visible
assert(not specialFields[fld]);

Changed: 80,81c139,153
local m = rawget(class, "parent"); if not m then return nil; end;
m = m[method]; if m then return m(self, ...); end;
--print("inherited", fld, class._className);
local m = rawget(class, "_parent");
if m then
m = findField(m, fld); -- search upwards
if m then return m(self, ...); end;
end;
end;
--local env = { method = fld };
--setmetatable(env, seeallmt); setfenv(inh, env);
--print(tostring(_ENV), tostring(package.seeall));
local seeallmt;
if newlua then
seeallmt = { __index = _ENV };
else
seeallmt = { __index = _G };

Removed: 83,84d154
local env = { method = fld };
setmetatable(env, seeallmt); setfenv(inh, env);

Changed: 88c158
rawset(class, fld, value);
rawset(specialFields[fld] and class or fields, fld, value);

Changed: 92,98c162
-- metatable for instances
local instmt = {
__index = FindField?,
__newindex = NewField?,
};


local classNew;

Changed: 100c164
-- check if the given object/class is instance of a given class (or one of it's parents)
-- create new class (with possible inheritance)

Changed: 102,105c166,169
-- class obj
-- class/instantiated class (object)
-- class class
-- class defined thru class(...)/object
-- class parent
-- parent or nil
-- nil/str className
-- class name

Changed: 107,118c171,192
-- true/false
local function InstanceOf? (obj, class)
-- check obj
local mt = getmetatable(class);
if mt == instmt then
-- this is an instance, get class
class = rawget(class, "parent");
end;
-- now go down to see if we are one of the sons of class
while obj do
if obj == class then return true; end;
obj = rawget(obj, "parent");
-- class
function class (parent, className)
if className == nil and type(parent) == "string" then className, parent = parent, nil; end;
local res = {
New = classNew,
_className = className or "<unnamed>",
_parent = parent,
_fields = {},
__call = classNew,
__index = findField,
__newindex = newField,
};
setmetatable(res, res);
if not res.isA then
-- no 'isA' method, add it
res.isA = function (self, class)
while self do
if self == class then return true; end;
self = rawget(self, "_parent");
end;
return false;
end;

Changed: 120c194
return false;
return res;

Changed: 125c199
-- (method) create new class instance
-- create new class instance

Changed: 133,144c207,225
local function ClassNew? (class, ...)
local res = {
classname = class.classname,
parent = class,
--__call = ClassNew?, --??? my stupid bug? %-)
InstanceOf? = InstanceOf?,
};
-- this trick registers "object finalizer"
local proxy = newproxy(true); -- create proxy (userdata) with empty metatable
local mt = getmetatable(proxy);
mt.__gc = function ()
local dd = res.Destructor; if dd then dd(res); end;
classNew = function (classDef, ...)
local res = class(classDef, rawget(classDef, "_className"));
if not newlua then
-- this trick registers "object finalizer" in Lua 5.1
local proxy = newproxy(true); -- create proxy (userdata) with empty metatable
local mt = getmetatable(proxy);
mt.__gc = function ()
local dd = res.Destructor;
if dd then dd(res); end;
end;
rawset(res, "__gc", proxy);
else
-- this trick registers "object finalizer" in Lua 5.2
local mt = getmetatable(res);
rawset(mt, "__gc", function ()
local dd = res.Destructor;
if dd then dd(res); end;
end);
setmetatable(res, mt);

Removed: 146d226
res.__gc = proxy;

Removed: 148d227
setmetatable(res, instmt);

Changed: 154,173c233,236
-- metatable for classes
local classmt = {
__index = FindField?,
__call = ClassNew?,
__newindex = NewField?,
};

-- $DESCR
-- create new class (with possible inheritance)
-- $IN
-- class parent
-- parent or nil
-- nil/str classname
-- class name (not used by klass)
-- $OUT
-- class
function class (parent, classname)
local res = { classname = classname, parent = parent, New = ClassNew?, AncestorOf? = InstanceOf? };
setmetatable(res, classmt);
return res;
if newlua then
_klassG.class = class;
else
_G.class = class;

Removed: 175,177d237


module("klass");

Changed: 187c247
local BaseClass? = class(nil, "base");
local BaseClass? = class("base");

Changed: 260,269c320,321
print("nc:InstanceOf?(nc): "..tostring(nc:InstanceOf?(bc)));
print("nc:InstanceOf?(NewClass?): "..tostring(nc:InstanceOf?(BaseClass?)));
print("nc:InstanceOf?(bc): "..tostring(nc:InstanceOf?(bc)));
print("nc:InstanceOf?(BaseClass?): "..tostring(nc:InstanceOf?(BaseClass?)));
print("bc:InstanceOf?(bc): "..tostring(bc:InstanceOf?(bc)));
print("bc:InstanceOf?(BaseClass?): "..tostring(bc:InstanceOf?(BaseClass?)));
print("bc:InstanceOf?(nc): "..tostring(bc:InstanceOf?(nc)));

print("NewClass?:AncestorOf?(BaseClass?): "..tostring(NewClass?:AncestorOf?(BaseClass?)));
print("BaseClass?:AncestorOf?(NewClass?): "..tostring(BaseClass?:AncestorOf?(NewClass?)));
print("nc:isA(bc): "..tostring(nc:isA(bc)));
print("nc:isA(BaseClass?): "..tostring(nc:isA(BaseClass?)));

Removed: 273d324
--[[

Removed: 277d327
]]

Another implementation of classes. Works with Lua 5.1 and Lua 5.2. Not the fastest one, but takes only about 2k (without comments), introduces "inherited(self, ...)" calls, has isA() method, and constructors. Destructors are also implemented, via unofficial/unsupported newproxy() trick or with "__gc" metamethod.

-- $FILE klass.lua
-- $VERSION 0.0.5
-- $AUTHOR Ketmar
-- $DESCRIPTION
--   another simple prototype-based class engine with finalizers and support for 'inherited' calls
--
-- $LICENSE
--   WTFPL
--
-- $DEPENDS
--   kmodule
--
-- $USAGE
--   local newclass = class(nil, "baseclass")
--   function newclass:Method (...) print(self); end;
--
--   local class2 = class(newclass)
--   function class2:Method (...) print(self); inherited(self, ...); end;
--
--   local inst = class2("argument to Constructor!");
--
--   iherited() will do nothing if there's no inherited method. %-)
--   class can have 2 special methods:
--     :Constructor (...)
--     :Destructor ()
--   `Destructor' will be called when class is going to be destroyed by GC
--
--   DO NOT FORGET TO PASS `self' TO inherited()!
--
--   earch method can know it's name from variable (not field!) method
--
--   one can add/alter methods in class or instance in runtime.
--
-- $HISTORY
--   [+] works with Lua 5.2 beta
--   [+] added :isA(obj)
--

-- cache global functions
local assert, type, rawget, rawset = assert, type, rawget, rawset;
local getmetatable, setmetatable = getmetatable, setmetatable;
--local newproxy = newproxy;
local setfenv, getfenv = setfenv, getfenv;
local getinfo, getupvalue, setupvalue, upvaluejoin;
local newlua = false;
local _klassG;

--local print, tostring = print, tostring;


if not setfenv then
  require "debug";
  getinfo, getupvalue, setupvalue, upvaluejoin = debug.getinfo, debug.getupvalue, debug.setupvalue, debug.upvaluejoin;
  newlua = true;

  setfenv = function (f, t)
    f = (type(f) == 'function' and f or getinfo(f+1, 'f').func);
    local up, name = 0;
    repeat
      up = up+1;
      name = getupvalue(f, up)
    until name == '_ENV' or name == nil;
    if name then
      upvaluejoin(f, up, function() return name; end, 1); -- use unique upvalue
      setupvalue(f, up, t);
    end;
  end;

  getfenv = function (f)
    f = (type(f) == 'function' and f or getinfo(f+1, 'f').func);
    local up, name, val = 0;
    repeat
      up = up+1;
      name, val = getupvalue(f, up);
    until name == '_ENV' or name == nil;
    return val;
  end;

  _klassG = _ENV;
end;


module(..., package.seeall);


local specialFields = {
  _className=true,
  _parent=true,
};


-- $DESCR
--   find field in class or in it's parents
-- $IN
--   class
--     class definition or instance
--   fld
--     field name (of any type)
-- $OUT
--  $OR good
--   value
--  $OR bad
--   nil
local function findField (class, fld)
  if specialFields[fld] then return rawget(class, fld); end;
  local res;
  repeat
    --print("findField", fld, class._className);
    res = rawget(rawget(class, "_fields"), fld); if res ~= nil then return res; end;
    -- not found, go up
    class = rawget(class, "_parent");
  until not class;
  -- default result: nil
end;


-- metatable for 'see everything'
--local seeallmt = { __index = _G };

-- $DESCR
--   add field or method, install environment for method
-- $IN
--   class
--     class definition or instance
--   fld
--     field name (of any type)
--   value
--     field value or metod
-- $OUT
local function newField (class, fld, value)
  local fields = rawget(class, "_fields");
  if type(value) == "function" then
    -- new method, change envtable to make inherited() visible
    assert(not specialFields[fld]);
    local inh = function (self, ...)
      --print("inherited", fld, class._className);
      local m = rawget(class, "_parent");
      if m then
        m = findField(m, fld); -- search upwards
        if m then return m(self, ...); end;
      end;
    end;
    --local env = { method = fld };
    --setmetatable(env, seeallmt); setfenv(inh, env);
    --print(tostring(_ENV), tostring(package.seeall));
    local seeallmt;
    if newlua then
      seeallmt = { __index = _ENV };
    else
      seeallmt = { __index = _G };
    end;
    local env = { method = fld, inherited = inh };
    setmetatable(env, seeallmt); setfenv(value, env);
  end;
  rawset(specialFields[fld] and class or fields, fld, value);
end;


local classNew;
-- $DESCR
--   create new class (with possible inheritance)
-- $IN
--   class parent
--     parent or nil
--   nil/str className
--     class name
-- $OUT
--   class
function class (parent, className)
  if className == nil and type(parent) == "string" then className, parent = parent, nil; end;
  local res = {
    New = classNew,
    _className = className or "<unnamed>",
    _parent = parent,
    _fields = {},
    __call = classNew,
    __index = findField,
    __newindex = newField,
  };
  setmetatable(res, res);
  if not res.isA then
    -- no 'isA' method, add it
    res.isA = function (self, class)
      while self do
        if self == class then return true; end;
        self = rawget(self, "_parent");
      end;
      return false;
    end;
  end;
  return res;
end;


-- $DESCR
--   create new class instance
-- $IN
--   class
--     class definition
--   ...
--     constructor args
-- $OUT
--   class_instance
classNew = function (classDef, ...)
  local res = class(classDef, rawget(classDef, "_className"));
  if not newlua then
    -- this trick registers "object finalizer" in Lua 5.1
    local proxy = newproxy(true); -- create proxy (userdata) with empty metatable
    local mt = getmetatable(proxy);
    mt.__gc = function ()
      local dd = res.Destructor;
      if dd then dd(res); end;
    end;
    rawset(res, "__gc", proxy);
  else
    -- this trick registers "object finalizer" in Lua 5.2
    local mt = getmetatable(res);
    rawset(mt, "__gc", function ()
      local dd = res.Destructor;
      if dd then dd(res); end;
    end);
    setmetatable(res, mt);
  end;
  -- end of "object finalizer" trick
  local cc = res.Constructor; if cc then cc(res, ...); end;
  return res;
end;


if newlua then
  _klassG.class = class;
else
  _G.class = class;
end;

and a testcase:

require "klass";

print("\n\n===============================");


local BaseClass = class("base");
function BaseClass:Constructor (...)
  print("BaseClass."..method);
end;

function BaseClass:Me (...)
  print("BaseClass."..method);
  print(self);
end;

function BaseClass:ClassName (...)
  print("BaseClass."..method);
  return "BaseClass";
end;

function BaseClass:Test ()
  print("BaseClass."..method);
  inherited(self);
end;

function BaseClass:Destructor ()
  print("BaseClass."..method);
  print(self);
end;


local NewClass = class(BaseClass, "new");
function NewClass:Constructor (...)
  print("NewClass."..method);
  inherited(self);
end;

function NewClass:ClassName (...)
  print("NewClass."..method);
  return "NewClass";
end;

function NewClass:Destructor ()
  print("NewClass."..method);
  print(self);
  inherited(self);
  print("NewClass instance is dead");
end;


print("\27[37;1mcreating BaseClass instance...\27[0m");
local bc = BaseClass();
print("\27[37;1mcreating NewClass instance...\27[0m");
local nc = NewClass();

function nc:Test ()
  print("!!!");
  inherited(self);
end;

print("\27[37;1mbc:ClassName()\27[0m");
print(bc:ClassName());
print("\27[37;1mnc:ClassName()\27[0m");
print(nc:ClassName());

print("\27[37;1mbc:Me()\27[0m");
bc:Me();
print("\27[37;1mnc:Me()\27[0m");
nc:Me();
print("\27[37;1mnc:Test()\27[0m");
nc:Test();

--[[
print(BaseClass);
print(NewClass);
print(nc.parent);
print(nc.parent.parent);
]]
print("nc:isA(bc): "..tostring(nc:isA(bc)));
print("nc:isA(BaseClass): "..tostring(nc:isA(BaseClass)));

print("\27[37;1mdone\27[0m");

bc = nil; nc = nil;
collectgarbage("collect");
collectgarbage("collect");

RecentChanges · preferences
edit · history
Last edited August 30, 2011 3:03 pm GMT (diff)