Skip to content

Instantly share code, notes, and snippets.

@nyaocat
Last active October 20, 2018 15:49
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nyaocat/7380582 to your computer and use it in GitHub Desktop.
Save nyaocat/7380582 to your computer and use it in GitHub Desktop.
Lua Read Only Table (or Userdata) module http://nyaocat.hatenablog.jp/entry/2013/11/09/111856
--[[
lua readonly module
2013 (c) nyaocat
this program is licensed under NYSL( http://www.kmonos.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 module, getfenv = module, getfenv -- for Lua5.1
local readonly_table_tag = newproxy() -- unique tag
local readonly
if ... and module then
-- Lua5.1
module(..., package.seeall)
readonly = getfenv(1)
else
readonly = {}
end
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)
if ((type(tbl[key]) == "table") or (type(tbl[key]) == "userdata")) then
local mt_ = getmetatable(tbl[key])
if mt_ and mt_.__type == readonly_table_tag then
return tbl[key]
else
return readonly.readonly(tbl[key])
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
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
elseif ((type(tbl[key]) == "table") or (type(tbl[key]) == "userdata")) then
local mt_ = getmetatable(tbl[key])
if mt_ and mt_.__type == readonly_table_tag then
return tbl[key]
else
return readonly.readonly2(tbl[key])
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 (...) 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment