--[[
lua readonly module
2013 (c) nyaocat
this program is licensed under NYSL( http://w...content-available-to-author-only...s.net/nysl/ )
lua5.1 and lua5.2
]]
local newproxy = newproxy or require("newproxy") -- for Lua5.2
local type, getmetatable, pairs, assert, error = type, getmetatable, pairs, assert, error
local next, ipairs, pairs = next, ipairs, pairs
local modname = ...
local readonly_table_tag = newproxy() -- unique tag
local readonly = {}
function readonly.next(t, index )
local mt = getmetatable(t)
if mt and mt.__type == readonly_table_tag then
return getmetatable(t).__next(t, index )
else
return next(t, index)
end
end
function readonly.pairs(t)
local mt = getmetatable(t)
if mt and mt.__type == readonly_table_tag then
return getmetatable(t).__next, t, nil
else
return pairs(t)
end
end
local function iter(tbl, i)
i = i + 1
local var = tbl[i]
if var then
return i, var
end
end
function readonly.ipairs(t)
local mt = getmetatable(t)
if mt and mt.__type == readonly_table_tag then
return iter, t, 0
else
return ipairs(t)
end
end
function readonly.readonly(tbl)
assert(type(tbl) == "table" or type(tbl) == "userdata",
"Only user data or table is possible to read-only.")
local ret = newproxy(true) -- ※
local mt = getmetatable(ret)
local tbl_mt = getmetatable(tbl)
if tbl_mt then
for k, v in pairs(tbl_mt) do
if k:match("^__") then
mt[k] = function(...) return v(...) end
end
end
end
mt.__len = function() return #tbl end
mt.__index = function(ud, key) return tbl[key] end
mt.__newindex = function()
error("value of a read-only table can not be updated.")
end
mt.__type = readonly_table_tag
mt.__next = function(t, index ) return next(tbl, index) end
return ret
end
function readonly.readonly2(tbl) -- member function can be updated.
assert(type(tbl) == "table" or type(tbl) == "userdata",
"Only user data or table is possible to read-only.")
local ret = newproxy(true)
local mt = getmetatable(ret)
local tbl_mt = getmetatable(tbl)
if tbl_mt then
for k, v in pairs(tbl_mt) do
if k:match("^__") then
mt[k] = function(...) return v(...) end
end
end
end
mt.__len = function() return #tbl end
mt.__index = function(ud, key)
if (type(tbl[key]) == "function") then
return function(self, ...) return tbl[key](tbl, ... ) end
else
return tbl[key]
end
end
mt.__newindex = function()
error("value of a read-only table can not be updated.")
end
mt.__type = readonly_table_tag
mt.__next = function(t, index ) return next(tbl, index) end
return ret
end
-- tests
if not (modname) then
local print, pcall = print, pcall
local ret, err = pcall(function()
arr = readonly.readonly {3, 9, 7, 4}
assert(pcall(function() local val = arr[2] end) == true )
assert(pcall(function() arr[1] = 98 end) == false)
t1 = readonly.readonly {
val = 10,
getVal = function(self) return self.val end,
setVal = function(self, newval) self.val = newval end
}
assert(pcall(function() t1:setVal(20) end) == false )
assert(pcall(function() t1:getVal() end) == true )
assert(pcall(function() t1.val = 30 end) == false )
t2 = readonly.readonly2 {
val = 10,
getVal = function(self) return self.val end,
setVal = function(self, newval) self.val = newval end
}
assert(pcall(function() t2:setVal(20) end) == true )
assert(pcall(function() t2:getVal() end) == true )
assert(pcall(function() t2.val = 30 end) == false )
end)
if ret then
print("tests all ok")
else
print("test failed ...", err)
end
end
return readonly