-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtables.lua
173 lines (142 loc) · 4.1 KB
/
tables.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
local ErrorBuilder = import("errors.lua")
---------------------
-- Table Utilities --
---------------------
-- Returns index if an array-equivalent table contains some value.
local function contains(tbl, value)
for index, tbl_value in pairs(tbl) do
if tbl_value == value then
return index
end
end
end
local copy_err = ErrorBuilder:new("tables->copy", 2)
-- Returns a deep copy of a table including metatables.
local function copy(original, ignore_type, _tbl_index)
if not ignore_type then
copy_err:assert(type(original) == "table", "argument must be a table (found %s)", type(original))
end
if not _tbl_index then _tbl_index = {} end
local clone
if type(original) == "table" then
clone = {}
for key, value in pairs(original) do
local value_type = type(value)
if value_type ~= "table" or not contains(_tbl_index, tostring(value)) then
if value_type == "table" then
table.insert(_tbl_index, tostring(value))
end
clone[copy(key, true, _tbl_index)] = copy(value, true, _tbl_index)
end
end
-- Handle metatables
local mt = getmetatable(original); if mt then
setmetatable(clone, copy(mt, true, _tbl_index))
end
else
clone = original
end
return clone
end
-- Inverts a table.
local function invert(tbl)
local inverted = {}
for key, value in pairs(tbl) do
inverted[value] = key
end
return inverted
end
-- Returns the number of items in a table.
local function count(tbl)
local number = 0
for _ in pairs(tbl) do number = number + 1 end
return number
end
-- Executes a function for every item in a table, passing the item as an argument.
local function foreach(tbl, func)
for _, item in pairs(tbl) do
local retval = func(item)
if retval ~= nil then return retval end
end
end
-- Prints the contents of a table.
local function dump(val, indent, tables_printed)
if not tables_printed then tables_printed = {} end
if type(val) == "string" then
return "\""..val.."\""
elseif type(val) == "table" then
if contains(tables_printed, tostring(val)) then
return string.rep(" ", indent) .. tostring(val)
else table.insert(tables_printed, tostring(val)) end
local res = ""
indent = indent or 4 -- Use existing indent or start at 4
if indent == 4 then
res = "{\n"
end
for key, value in pairs(val) do
-- Indent, unless the current table is an array.
res = res .. string.rep(" ", indent)
if type(value) == "table" then
res = res .. string.format("%s (%s) = {\n%s\n%s}\n", tostring(key), tostring(value), dump(value, (indent + 4),
tables_printed), string.rep(" ", indent))
else
res = res .. string.format("%s = %s\n", tostring(key), dump(value))
end
end
if indent == 4 then
res = res .. "}"
else
res = res:sub(1, -2)
end
return res
else
return tostring(val)
end
end
local static_err = ErrorBuilder:new("tables->static", 2)
-- Modifies the metatable of a table to make it read-only or to otherwise control access.
local function static(table, meta, ctrl)
if not meta then meta = {} end
local abstract = {}
-- Prevent modifying the table directly.
function meta.__newindex(tbl, key, value)
static_err:throw("attempt to modify read-only table '%s'", table)
end
local old_index = meta.__index
-- Forward access attempts to the real table.
function meta.__index(tbl, key)
local raw_value = table[key]
if old_index and type(old_index) == "function" then
raw_value = old_index(tbl, key)
end
if not ctrl then
return raw_value
end
static_err:assert(ctrl._mode == "whitelist" or ctrl._mode == "blacklist", "invalid control mode '%s'",
ctrl._mode)
-- Loop over control table, ignoring the mode.
for ctrl_key, value in pairs(ctrl) do
if ctrl_key ~= "_mode" and key == value then
return ctrl._mode == "whitelist" and raw_value or nil
end
end
-- if we reach this point and the mode is blacklist, the key is allowed
if ctrl._mode == "blacklist" then
return raw_value
end
end
setmetatable(abstract, meta)
return abstract
end
-------------
-- Exports --
-------------
return {
copy = copy,
contains = contains,
invert = invert,
count = count,
foreach = foreach,
dump = dump,
static = static
}