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