Last active
October 20, 2018 15:49
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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