-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathcontainer.lua
More file actions
306 lines (245 loc) · 8.06 KB
/
Copy pathcontainer.lua
File metadata and controls
306 lines (245 loc) · 8.06 KB
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
--[[--
Container prototype.
A container is a @{std.object} with no methods. It's functionality is
instead defined by its *meta*methods.
Where an Object uses the `__index` metatable entry to hold object
methods, a Container stores its contents using `__index`, preventing
it from having methods in there too.
Although there are no actual methods, Containers are free to use
metamethods (`__index`, `__sub`, etc) and, like Objects, can supply
module functions by listing them in `_functions`. Also, since a
@{std.container} is a @{std.object}, it can be passed to the
@{std.object} module functions, or anywhere else a @{std.object} is
expected.
When making your own prototypes, derive from @{std.container} if you want
to access the contents of your objects with the `[]` operator, or from
@{std.object} if you want to access the functionality of your objects with
named object methods.
Prototype Chain
---------------
table
`-> Object
`-> Container
@classmod std.container
]]
local _DEBUG = require "std.debug_init"._DEBUG
local base = require "std.base"
local debug = require "std.debug"
local ipairs, pairs, okeys = base.ipairs, base.pairs, base.okeys
local insert, len, maxn = base.insert, base.len, base.maxn
local okeys, prototype, tostring = base.okeys, base.prototype, base.tostring
local argcheck = debug.argcheck
--[[ ================= ]]--
--[[ Helper Functions. ]]--
--[[ ================= ]]--
-- Instantiate a new object based on *proto*.
--
-- This is equivalent to:
--
-- table.merge (table.clone (proto), t or {})
--
-- But, not typechecking arguments or checking for metatables, is
-- slightly faster.
-- @tparam table proto base object to copy from
-- @tparam[opt={}] table t additional fields to merge in
-- @treturn table a new table with fields from proto and t merged in.
local function instantiate (proto, t)
local obj = {}
local k, v = next (proto)
while k do
obj[k] = v
k, v = next (proto, k)
end
t = t or {}
k, v = next (t)
while k do
obj[k] = v
k, v = next (t, k)
end
return obj
end
local ModuleFunction = {
__tostring = function (self) return tostring (self.call) end,
__call = function (self, ...) return self.call (...) end,
}
--- Mark a function not to be copied into clones.
--
-- It responds to `type` with `table`, but otherwise behaves like a
-- regular function. Marking uncopied module functions in-situ like this
-- (as opposed to doing book keeping in the metatable) means that we
-- don't have to create a new metatable with the book keeping removed for
-- cloned objects, we can just share our existing metatable directly.
-- @func fn a function
-- @treturn functable a callable functable for `fn`
local function modulefunction (fn)
if getmetatable (fn) == ModuleFunction then
-- Don't double wrap!
return fn
else
return setmetatable ({_type = "modulefunction", call = fn}, ModuleFunction)
end
end
--[[ ================= ]]--
--[[ Container Object. ]]--
--[[ ================= ]]--
local function mapfields (obj, src, map)
local mt = getmetatable (obj) or {}
-- Map key pairs.
-- Copy all pairs when `map == nil`, but discard unmapped src keys
-- when map is provided (i.e. if `map == {}`, copy nothing).
if map == nil or next (map) then
map = map or {}
local k, v = next (src)
while k do
local key, dst = map[k] or k, obj
local kind = type (key)
if kind == "string" and key:sub (1, 1) == "_" then
mt[key] = v
elseif next (map) and kind == "number" and len (dst) + 1 < key then
-- When map is given, but has fewer entries than src, stop copying
-- fields when map is exhausted.
break
else
dst[key] = v
end
k, v = next (src, k)
end
end
-- Quicker to remove this after copying fields than test for it
-- it on every iteration above.
mt._functions = nil
-- Inject module functions.
local t = src._functions or {}
local k, v = next (t)
while (k) do
obj[k] = modulefunction (v)
k, v = next (t, k)
end
-- Only set non-empty metatable.
if next (mt) then
setmetatable (obj, mt)
end
return obj
end
local function __call (self, x, ...)
local mt = getmetatable (self)
local obj_mt = mt
local obj = {}
-- This is the slowest part of cloning for any objects that have
-- a lot of fields to test and copy. If you need to clone a lot of
-- objects from a prototype with several module functions, it's much
-- faster to clone objects from each other than the prototype!
local k, v = next (self)
while (k) do
if type (v) ~= "table" or v._type ~= "modulefunction" then
obj[k] = v
end
k, v = next (self, k)
end
if type (mt._init) == "function" then
obj = mt._init (obj, x, ...)
else
obj = (self.mapfields or mapfields) (obj, x, mt._init)
end
-- If a metatable was set, then merge our fields and use it.
if next (getmetatable (obj) or {}) then
obj_mt = instantiate (mt, getmetatable (obj))
-- Merge object methods.
if type (obj_mt.__index) == "table" and
type ((mt or {}).__index) == "table"
then
obj_mt.__index = instantiate (mt.__index, obj_mt.__index)
end
end
return setmetatable (obj, obj_mt)
end
local function X (decl, fn)
return debug.argscheck ("std.container." .. decl, fn)
end
local M = {
mapfields = X ("mapfields (table, table|object, ?table)", mapfields),
}
if _DEBUG.argcheck then
local argerror, extramsg_toomany = debug.argerror, debug.extramsg_toomany
M.__call = function (self, x, ...)
local mt = getmetatable (self)
-- A function initialised object can be passed arguments of any
-- type, so only argcheck non-function initialised objects.
if type (mt._init) ~= "function" then
local name, argt = mt._type, {...}
-- Don't count `self` as an argument for error messages, because
-- it just refers back to the object being called: `Container {"x"}.
argcheck (name, 1, "table", x)
if next (argt) then
argerror (name, 2, extramsg_toomany ("argument", 1, 1 + maxn (argt)), 2)
end
end
return __call (self, x, ...)
end
else
M.__call = __call
end
function M.__tostring (self)
local n, k_ = 1, nil
local buf = { prototype (self), " {" } -- pre-buffer object open
for _, k in ipairs (okeys (self)) do -- for ordered public members
local v = self[k]
if k_ ~= nil then -- | buffer separator
if k ~= n and type (k_) == "number" and k_ == n - 1 then
-- `;` separates `v` elements from `k=v` elements
buf[#buf + 1] = "; "
elseif k ~= nil then
-- `,` separator everywhere else
buf[#buf + 1] = ", "
end
end
if type (k) == "number" and k == n then -- | buffer key/value pair
-- render initial array-like elements as just `v`
buf[#buf + 1] = tostring (v)
n = n + 1
else
-- render remaining elements as `k=v`
buf[#buf + 1] = tostring (k) .. "=" .. tostring (v)
end
k_ = k -- maintain loop invariant: k_ is previous key
end
buf[#buf + 1] = "}" -- buffer object close
return table.concat (buf) -- stringify buffer
end
--- Container prototype.
--
-- Container also inherits all the fields and methods from
-- @{std.object.Object}.
-- @object Container
-- @string[opt="Container"] _type object name
-- @see std.object
-- @see std.object.__call
-- @usage
-- local std = require "std"
-- local Container = std.container {}
--
-- local Graph = Container {
-- _type = "Graph",
-- _functions = {
-- nodes = function (graph)
-- local n = 0
-- for _ in std.pairs (graph) do n = n + 1 end
-- return n
-- end,
-- },
-- }
-- local g = Graph { "node1", "node2" }
-- --> 2
-- print (Graph.nodes (g))
return setmetatable ({
-- Normally, these are set and wrapped automatically during cloning.
-- But, we have to bootstrap the first object, so in this one instance
-- it has to be done manually.
mapfields = modulefunction (M.mapfields),
prototype = modulefunction (prototype),
}, {
_type = "Container",
__call = M.__call,
__tostring = M.__tostring,
__pairs = M.__pairs,
})