--oo/oo-system/vobject: object class with virtual properties --Written by Cosmin Apreutesei. Public Domain. --indexing attribute returns the result of class:get_(). --setting on attribute calls class:set_(value). --if there's a setter but no getter, gets stored in table --object.__state and class:set_(value) is called. --a property with a getter but no setter cannot be assigned to. --a property with no getter and no setter is rawset. setfenv(1, require'winapi') require'winapi.object' VObject = class(Object) function VObject:__subclass(c) --class constructor VObject.__index.__subclass(self,c) c.__meta.__index = function(o,k) if c[k] ~= nil then return c[k] end --class property or method return c:__get_vproperty(o,k) end c.__meta.__newindex = function(o,k,v) c:__set_vproperty(o,k,v) end end function VObject:__get_vproperty(o,k) if type(k) == 'string' and self['get_'..k] then return self['get_'..k](o) elseif rawget(o, '__state') then return o.__state[k] end end function VObject:__set_vproperty(o,k,v) if type(k) == 'string' then if self['get_'..k] then if self['set_'..k] then --r/w property self['set_'..k](o,v) else --r/o property error(string.format('trying to set read only property "%s"', k), 2) end elseif self['set_'..k] then --stored property if not rawget(o, '__state') then rawset(o, '__state', {}) end o.__state[k] = v self['set_'..k](o,v) else rawset(o,k,v) end else rawset(o,k,v) end end function VObject:__gen_vproperties(names, getter, setter) for k in pairs(names) do if getter then self['get_'..k] = function(self) return getter(self, k) end end if setter then self['set_'..k] = function(self, v) return setter(self, k, v) end end end end --introspection function VObject:get___class() return getmetatable(self).__class end function VObject:__vproperties() --returns iter() -> property, {get = class, set = class} local t = {} for k,v,source in VObject.__index.__allpairs(self) do if type(k) == 'string' and k:find'^get_' or k:find'^set_' then local k,what = k:sub(5), k:sub(1,3) local t = t[k] or {}; t[k] = t[k] or t if what == 'get' and t.get == nil then t.get = source end if what == 'set' and t.set == nil then t.set = source end end end return pairs(t) end --showcase if not ... then local STATUS = 'OK' local c = class(VObject) function c:__init(...) print('init:', ...) end function c:get_status() return STATUS end function c:set_status(s) STATUS = s end function c:set_text(s) end local o = c('hi', 'there') assert(o.status == 'OK') --virtual property get o.status = 'EVEN BETTER' --virtual property set assert(o.__state == nil) --no active properties yet o.text = 'hello' --active property set assert(o.text == 'hello') --active property get assert(o.__state.text == o.text) --confirm the active property assert(o.unknown == nil) --non-existent property assert(o[1234] == nil) --non-existent property assert(o[false] == nil) --non-existent property --isinstance assert(isinstance(o, VObject) == true) assert(isinstance(o, c) == true) assert(isinstance(o, o) == false) --introspection for k,v in o:__vproperties() do _G.print(k, v.get, v.set) end end