Merge branch 'index-newindex' of https://github.com/mpeterv/middleclass into mpeterv-index-newindex

This commit is contained in:
kikito 2015-12-10 09:25:13 +01:00
commit 79a7e02954
2 changed files with 123 additions and 13 deletions

View File

@ -30,14 +30,14 @@ local middleclass = {
local _metamethods = {}
for m in ([[ add band bor bxor bnot call concat div eq
gc ipairs idiv le len lt metatable mod mode
mul pairs pow shl shr sub tostring unm ]]):gmatch("%S+") do
gc index ipairs idiv le len lt metatable mod mode
mul newindex pairs pow shl shr sub tostring unm ]]):gmatch("%S+") do
_metamethods['__' .. m] = true
end
local function _setClassDictionariesMetatables(aClass)
local dict = aClass.__instanceDict
dict.__index = dict
aClass.__instanceMeta.__index = dict
local super = aClass.super
if super then
@ -49,30 +49,54 @@ local function _setClassDictionariesMetatables(aClass)
end
end
local function _createIndexWrapper(aClass, f)
if f == nil then
return aClass.__instanceDict
else
return function(self, key)
local value = aClass.__instanceDict[key]
if value ~= nil then
return value
elseif type(f) == "function" then
return (f(self, key))
else
return f[key]
end
end
end
end
local function _setMetamethod(aClass, name, f)
if name == "__index" then
f = _createIndexWrapper(aClass, f)
end
aClass.__instanceMeta[name] = f
end
local function _propagateMetamethod(aClass, name, f)
_setMetamethod(aClass, name, f)
for subclass in pairs(aClass.subclasses) do
if not subclass.__metamethods[name] then
subclass.__instanceDict[name] = f
if rawget(subclass.__instanceDict, name) == nil then
_propagateMetamethod(subclass, name, f)
end
end
end
local function _updateClassDict(aClass, key, value)
aClass.__instanceDict[key] = value
if _metamethods[key] then
if value == nil then
aClass.__metamethods[key] = nil
if aClass.super then
value = aClass.super.__instanceDict[key]
end
else
aClass.__metamethods[key] = true
end
_propagateMetamethod(aClass, key, value)
end
aClass.__instanceDict[key] = value
end
local function _setClassMetatable(aClass)
@ -85,7 +109,7 @@ local function _setClassMetatable(aClass)
end
local function _createClass(name, super)
local aClass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict = {}, __metamethods = {} }
local aClass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict = {}, __instanceMeta = {} }
aClass.subclasses = setmetatable({}, {__mode = "k"})
_setClassDictionariesMetatables(aClass)
@ -96,7 +120,7 @@ end
local function _setSubclassMetamethods(aClass, subclass)
for m in pairs(_metamethods) do
subclass.__instanceDict[m] = aClass.__instanceDict[m]
_setMetamethod(subclass, m, aClass.__instanceDict[m])
end
end
@ -124,7 +148,7 @@ local Object = _createClass("Object", nil)
function Object.static:allocate()
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = self }, self.__instanceDict)
return setmetatable({ class = self }, self.__instanceMeta)
end
function Object.static:new(...)

View File

@ -204,6 +204,92 @@ describe('Metamethods', function()
end)
end)
describe('Custom __index and __newindex', function()
describe('Tables', function()
local Proxy, fallback, p
before_each(function()
Proxy = class('Proxy')
fallback = {foo = 'bar', common = 'fallback'}
Proxy.__index = fallback
Proxy.__newindex = fallback
Proxy.common = 'class'
p = Proxy()
end)
it('uses __index', function()
assert.equal(p.foo, 'bar')
end)
it('does not use __index when field exists in class', function()
assert.equal(p.common, 'class')
end)
it('uses __newindex', function()
p.key = 'value'
assert.equal(fallback.key, 'value')
end)
it('uses __newindex when field exists in class', function()
p.common = 'value'
assert.equal(p.common, 'class')
assert.equal(Proxy.common, 'class')
assert.equal(fallback.common, 'value')
end)
end)
describe('Functions', function()
local Namespace, Rectangle, r
before_each(function()
Namespace = class('Namespace')
function Namespace:__index(name)
local getter = self.class[name.."Getter"]
if getter then return getter(self) end
end
function Namespace:__newindex(name, value)
local setter = self.class[name.."Setter"]
if setter then setter(self, value) else rawset(self, name, value) end
end
Rectangle = class('Rectangle', Namespace)
function Rectangle:initialize(x, y, scale)
self._scale, self.x, self.y = 1, x, y
self.scale = scale
end
function Rectangle:scaleGetter() return self._scale end
function Rectangle:scaleSetter(v)
self.x = self.x*v/self._scale
self.y = self.y*v/self._scale
self._scale = v
end
function Rectangle:areaGetter() return self.x * self.y end
r = Rectangle(3, 4, 2)
end)
it('uses setter', function()
assert.equal(r.x, 6)
assert.equal(r.y, 8)
r.scale = 3
assert.equal(r.x, 9)
assert.equal(r.y, 12)
end)
it('uses getters', function()
assert.equal(r.scale, 2)
assert.equal(r.area, 48)
end)
it('updates inherited __index', function()
function Namespace.__index() return 42 end
assert.equal(r.area, 42)
function Rectangle.__index() return 24 end
assert.equal(r.area, 24)
function Namespace.__index() return 96 end
assert.equal(r.area, 24)
Rectangle.__index = nil
assert.equal(r.area, 96)
end)
end)
end)
describe('Default Metamethods', function()
local Peter, peter