1--[[--
2 Container prototype.
3
4 A container is a @{std.object} with no methods.  It's functionality is
5 instead defined by its *meta*methods.
6
7 Where an Object uses the `__index` metatable entry to hold object
8 methods, a Container stores its contents using `__index`, preventing
9 it from having methods in there too.
10
11 Although there are no actual methods, Containers are free to use
12 metamethods (`__index`, `__sub`, etc) and, like Objects, can supply
13 module functions by listing them in `_functions`.  Also, since a
14 @{std.container} is a @{std.object}, it can be passed to the
15 @{std.object} module functions, or anywhere else a @{std.object} is
16 expected.
17
18 When making your own prototypes, derive from @{std.container} if you want
19 to access the contents of your objects with the `[]` operator, or from
20 @{std.object} if you want to access the functionality of your objects with
21 named object methods.
22
23 Prototype Chain
24 ---------------
25
26      table
27       `-> Object
28            `-> Container
29
30 @classmod std.container
31]]
32
33
34local _DEBUG = require "std.debug_init"._DEBUG
35
36local base  = require "std.base"
37local debug = require "std.debug"
38
39local ipairs, pairs, okeys = base.ipairs, base.pairs, base.okeys
40local insert, len, maxn = base.insert, base.len, base.maxn
41local okeys, prototype, tostring = base.okeys, base.prototype, base.tostring
42local argcheck = debug.argcheck
43
44
45
46--[[ ================= ]]--
47--[[ Helper Functions. ]]--
48--[[ ================= ]]--
49
50
51-- Instantiate a new object based on *proto*.
52--
53-- This is equivalent to:
54--
55--     table.merge (table.clone (proto), t or {})
56--
57-- But, not typechecking arguments or checking for metatables, is
58-- slightly faster.
59-- @tparam table proto base object to copy from
60-- @tparam[opt={}] table t additional fields to merge in
61-- @treturn table a new table with fields from proto and t merged in.
62local function instantiate (proto, t)
63  local obj = {}
64  local k, v = next (proto)
65  while k do
66    obj[k] = v
67    k, v = next (proto, k)
68  end
69
70  t = t or {}
71  k, v = next (t)
72  while k do
73    obj[k] = v
74    k, v = next (t, k)
75  end
76  return obj
77end
78
79
80local ModuleFunction = {
81  __tostring = function (self) return tostring (self.call) end,
82  __call     = function (self, ...) return self.call (...) end,
83}
84
85
86--- Mark a function not to be copied into clones.
87--
88-- It responds to `type` with `table`, but otherwise behaves like a
89-- regular function.  Marking uncopied module functions in-situ like this
90-- (as opposed to doing book keeping in the metatable) means that we
91-- don't have to create a new metatable with the book keeping removed for
92-- cloned objects, we can just share our existing metatable directly.
93-- @func fn a function
94-- @treturn functable a callable functable for `fn`
95local function modulefunction (fn)
96  if getmetatable (fn) == ModuleFunction then
97    -- Don't double wrap!
98    return fn
99  else
100    return setmetatable ({_type = "modulefunction", call = fn}, ModuleFunction)
101  end
102end
103
104
105
106--[[ ================= ]]--
107--[[ Container Object. ]]--
108--[[ ================= ]]--
109
110
111local function mapfields (obj, src, map)
112  local mt = getmetatable (obj) or {}
113
114  -- Map key pairs.
115  -- Copy all pairs when `map == nil`, but discard unmapped src keys
116  -- when map is provided (i.e. if `map == {}`, copy nothing).
117  if map == nil or next (map) then
118    map = map or {}
119    local k, v = next (src)
120    while k do
121      local key, dst = map[k] or k, obj
122      local kind = type (key)
123      if kind == "string" and key:sub (1, 1) == "_" then
124        mt[key] = v
125      elseif next (map) and kind == "number" and len (dst) + 1 < key then
126        -- When map is given, but has fewer entries than src, stop copying
127        -- fields when map is exhausted.
128        break
129      else
130        dst[key] = v
131      end
132      k, v = next (src, k)
133    end
134  end
135
136  -- Quicker to remove this after copying fields than test for it
137  -- it on every iteration above.
138  mt._functions = nil
139
140  -- Inject module functions.
141  local t = src._functions or {}
142  local k, v = next (t)
143  while (k) do
144    obj[k] = modulefunction (v)
145    k, v = next (t, k)
146  end
147
148  -- Only set non-empty metatable.
149  if next (mt) then
150    setmetatable (obj, mt)
151  end
152  return obj
153end
154
155
156local function __call (self, x, ...)
157  local mt     = getmetatable (self)
158  local obj_mt = mt
159  local obj    = {}
160
161  -- This is the slowest part of cloning for any objects that have
162  -- a lot of fields to test and copy.  If you need to clone a lot of
163  -- objects from a prototype with several module functions, it's much
164  -- faster to clone objects from each other than the prototype!
165  local k, v = next (self)
166  while (k) do
167    if type (v) ~= "table" or v._type ~= "modulefunction" then
168      obj[k] = v
169    end
170    k, v = next (self, k)
171  end
172
173  if type (mt._init) == "function" then
174    obj = mt._init (obj, x, ...)
175  else
176    obj = (self.mapfields or mapfields) (obj, x, mt._init)
177  end
178
179  -- If a metatable was set, then merge our fields and use it.
180  if next (getmetatable (obj) or {}) then
181    obj_mt = instantiate (mt, getmetatable (obj))
182
183    -- Merge object methods.
184    if type (obj_mt.__index) == "table" and
185      type ((mt or {}).__index) == "table"
186    then
187      obj_mt.__index = instantiate (mt.__index, obj_mt.__index)
188    end
189  end
190
191  return setmetatable (obj, obj_mt)
192end
193
194
195local function X (decl, fn)
196  return debug.argscheck ("std.container." .. decl, fn)
197end
198
199local M = {
200  mapfields = X ("mapfields (table, table|object, ?table)", mapfields),
201}
202
203
204if _DEBUG.argcheck then
205
206  local argerror, extramsg_toomany = debug.argerror, debug.extramsg_toomany
207
208  M.__call = function (self, x, ...)
209    local mt = getmetatable (self)
210
211    -- A function initialised object can be passed arguments of any
212    -- type, so only argcheck non-function initialised objects.
213    if type (mt._init) ~= "function" then
214      local name, argt = mt._type, {...}
215      -- Don't count `self` as an argument for error messages, because
216      -- it just refers back to the object being called: `Container {"x"}.
217      argcheck (name, 1, "table", x)
218      if next (argt) then
219        argerror (name, 2, extramsg_toomany ("argument", 1, 1 + maxn (argt)), 2)
220      end
221    end
222
223    return __call (self, x, ...)
224  end
225
226else
227
228  M.__call = __call
229
230end
231
232
233function M.__tostring (self)
234  local n, k_ = 1, nil
235  local buf = { prototype (self), " {" }	-- pre-buffer object open
236  for _, k in ipairs (okeys (self)) do		-- for ordered public members
237    local v = self[k]
238
239    if k_ ~= nil then				-- | buffer separator
240      if k ~= n and type (k_) == "number" and k_ == n - 1 then
241        -- `;` separates `v` elements from `k=v` elements
242        buf[#buf + 1] = "; "
243      elseif k ~= nil then
244	-- `,` separator everywhere else
245        buf[#buf + 1] = ", "
246      end
247    end
248
249    if type (k) == "number" and k == n then	-- | buffer key/value pair
250      -- render initial array-like elements as just `v`
251      buf[#buf + 1] = tostring (v)
252      n = n + 1
253    else
254      -- render remaining elements as `k=v`
255      buf[#buf + 1] = tostring (k) .. "=" .. tostring (v)
256    end
257
258    k_ = k -- maintain loop invariant: k_ is previous key
259  end
260  buf[#buf + 1] = "}"				-- buffer object close
261
262  return table.concat (buf)			-- stringify buffer
263end
264
265
266--- Container prototype.
267--
268-- Container also inherits all the fields and methods from
269-- @{std.object.Object}.
270-- @object Container
271-- @string[opt="Container"] _type object name
272-- @see std.object
273-- @see std.object.__call
274-- @usage
275-- local std = require "std"
276-- local Container = std.container {}
277--
278-- local Graph = Container {
279--   _type = "Graph",
280--   _functions = {
281--     nodes = function (graph)
282--       local n = 0
283--       for _ in std.pairs (graph) do n = n + 1 end
284--       return n
285--     end,
286--   },
287-- }
288-- local g = Graph { "node1", "node2" }
289-- --> 2
290-- print (Graph.nodes (g))
291
292return setmetatable ({
293
294  -- Normally, these are set and wrapped automatically during cloning.
295  -- But, we have to bootstrap the first object, so in this one instance
296  -- it has to be done manually.
297
298  mapfields = modulefunction (M.mapfields),
299  prototype = modulefunction (prototype),
300}, {
301  _type = "Container",
302
303  __call     = M.__call,
304  __tostring = M.__tostring,
305  __pairs    = M.__pairs,
306})
307