dfhack/library/lua/class.lua

159 lines
4.0 KiB
Lua

-- A trivial reloadable class system
local _ENV = mkmodule('class')
-- Metatable template for a class
class_obj = class_obj or {}
-- Methods shared by all classes
common_methods = common_methods or {}
-- Forbidden names for class fields and methods.
reserved_names = { super = true, ATTRS = true }
-- Attribute table metatable
attrs_meta = attrs_meta or {}
-- Create or updates a class; a class has metamethods and thus own metatable.
function defclass(class,parent)
class = class or {}
local meta = getmetatable(class)
if not meta then
meta = {}
setmetatable(class, meta)
end
for k,v in pairs(class_obj) do meta[k] = v end
meta.__index = parent or common_methods
local attrs = rawget(class, 'ATTRS') or {}
setmetatable(attrs, attrs_meta)
rawset(class, 'super', parent)
rawset(class, 'ATTRS', attrs)
rawset(class, '__index', rawget(class, '__index') or class)
return class
end
-- An instance uses the class as metatable
function mkinstance(class,table)
table = table or {}
setmetatable(table, class)
return table
end
-- Patch the stubs in the global environment
dfhack.BASE_G.defclass = _ENV.defclass
dfhack.BASE_G.mkinstance = _ENV.mkinstance
-- Just verify the name, and then set.
function class_obj:__newindex(name,val)
if reserved_names[name] or common_methods[name] then
error('Method name '..name..' is reserved.')
end
rawset(self, name, val)
end
function attrs_meta:__call(attrs)
for k,v in pairs(attrs) do
self[k] = v
end
end
local function apply_attrs(obj, attrs, init_table)
for k,v in pairs(attrs) do
if v == DEFAULT_NIL then
v = nil
end
obj[k] = init_table[k] or v
end
end
local function invoke_before_rec(self, class, method, ...)
local meta = getmetatable(class)
if meta then
local fun = rawget(class, method)
if fun then
fun(self, ...)
end
invoke_before_rec(self, meta.__index, method, ...)
end
end
local function invoke_after_rec(self, class, method, ...)
local meta = getmetatable(class)
if meta then
invoke_after_rec(self, meta.__index, method, ...)
local fun = rawget(class, method)
if fun then
fun(self, ...)
end
end
end
local function init_attrs_rec(obj, class, init_table)
local meta = getmetatable(class)
if meta then
init_attrs_rec(obj, meta.__index, init_table)
apply_attrs(obj, rawget(class, 'ATTRS'), init_table)
end
end
-- Call metamethod constructs the object
function class_obj:__call(init_table)
-- The table is assumed to be a scratch temporary.
-- If it is not, copy it yourself before calling.
init_table = init_table or {}
local obj = mkinstance(self)
-- This initialization sequence is broadly based on how the
-- Common Lisp initialize-instance generic function works.
-- preinit screens input arguments in subclass to superclass order
invoke_before_rec(obj, self, 'preinit', init_table)
-- initialize the instance table from init table
init_attrs_rec(obj, self, init_table)
-- init in superclass -> subclass
invoke_after_rec(obj, self, 'init', init_table)
-- postinit in superclass -> subclass
invoke_after_rec(obj, self, 'postinit', init_table)
return obj
end
-- Common methods for all instances:
function common_methods:callback(method, ...)
return dfhack.curry(self[method], self, ...)
end
function common_methods:cb_getfield(field)
return function() return self[field] end
end
function common_methods:cb_setfield(field)
return function(val) self[field] = val end
end
function common_methods:assign(data)
for k,v in pairs(data) do
self[k] = v
end
end
function common_methods:invoke_before(method, ...)
return invoke_before_rec(self, getmetatable(self), method, ...)
end
function common_methods:invoke_after(method, ...)
return invoke_after_rec(self, getmetatable(self), method, ...)
end
return _ENV