/metalualib/metalua/table2.lua

http://github.com/davidm/lua-inspect · Lua · 372 lines · 274 code · 44 blank · 54 comment · 138 complexity · 96a0675651a68535e5baa80b3e61cb3b MD5 · raw file

  1. ---------------------------------------------------------------------
  2. ----------------------------------------------------------------------
  3. --
  4. -- Table module extension
  5. --
  6. ----------------------------------------------------------------------
  7. ----------------------------------------------------------------------
  8. -- todo: table.scan (scan1?) fold1? flip?
  9. function table.transpose(t)
  10. local tt = { }
  11. for a, b in pairs(t) do tt[b] = a end
  12. return tt
  13. end
  14. function table.iforeach(f, ...)
  15. -- assert (type (f) == "function") [wouldn't allow metamethod __call]
  16. local nargs = select("#", ...)
  17. if nargs==1 then -- Quick iforeach (most common case), just one table arg
  18. local t = ...
  19. assert (type (t) == "table")
  20. for i = 1, #t do
  21. local result = f (t[i])
  22. -- If the function returns non-false, stop iteration
  23. if result then return result end
  24. end
  25. else -- advanced case: boundaries and/or multiple tables
  26. -- 1 - find boundaries if any
  27. local args, fargs, first, last, arg1 = {...}, { }
  28. if type(args[1]) ~= "number" then first, arg1 = 1, 1
  29. elseif type(args[2]) ~= "number" then first, last, arg1 = 1, args[1], 2
  30. else first, last, i = args[1], args[2], 3 end
  31. assert (nargs > arg1)
  32. -- 2 - determine upper boundary if not given
  33. if not last then for i = arg1, nargs do
  34. assert (type (args[i]) == "table")
  35. last = max (#args[i], last)
  36. end end
  37. -- 3 - perform the iteration
  38. for i = first, last do
  39. for j = arg1, nargs do fargs[j] = args[j][i] end -- build args list
  40. local result = f (unpack (fargs)) -- here is the call
  41. -- If the function returns non-false, stop iteration
  42. if result then return result end
  43. end
  44. end
  45. end
  46. function table.imap (f, ...)
  47. local result, idx = { }, 1
  48. local function g(...) result[idx] = f(...); idx=idx+1 end
  49. table.iforeach(g, ...)
  50. return result
  51. end
  52. function table.ifold (f, acc, ...)
  53. local function g(...) acc = f (acc,...) end
  54. table.iforeach (g, ...)
  55. return acc
  56. end
  57. -- function table.ifold1 (f, ...)
  58. -- return table.ifold (f, acc, 2, false, ...)
  59. -- end
  60. function table.izip(...)
  61. local function g(...) return {...} end
  62. return table.imap(g, ...)
  63. end
  64. function table.ifilter(f, t)
  65. local yes, no = { }, { }
  66. for i=1,#t do table.insert (f(t[i]) and yes or no, t[i]) end
  67. return yes, no
  68. end
  69. function table.icat(...)
  70. local result = { }
  71. for t in values {...} do
  72. for x in values (t) do
  73. table.insert (result, x)
  74. end
  75. end
  76. return result
  77. end
  78. function table.iflatten (x) return table.icat (unpack (x)) end
  79. function table.irev (t)
  80. local result, nt = { }, #t
  81. for i=0, nt-1 do result[nt-i] = t[i+1] end
  82. return result
  83. end
  84. function table.isub (t, ...)
  85. local ti, u = table.insert, { }
  86. local args, nargs = {...}, select("#", ...)
  87. for i=1, nargs/2 do
  88. local a, b = args[2*i-1], args[2*i]
  89. for i=a, b, a<=b and 1 or -1 do ti(u, t[i]) end
  90. end
  91. return u
  92. end
  93. function table.iall (f, ...)
  94. local result = true
  95. local function g(...) return not f(...) end
  96. return not table.iforeach(g, ...)
  97. --return result
  98. end
  99. function table.iany (f, ...)
  100. local function g(...) return not f(...) end
  101. return not table.iall(g, ...)
  102. end
  103. function table.shallow_copy(x)
  104. local y={ }
  105. for k, v in pairs(x) do y[k]=v end
  106. return y
  107. end
  108. -- Warning, this is implementation dependent: it relies on
  109. -- the fact the [next()] enumerates the array-part before the hash-part.
  110. function table.cat(...)
  111. local y={ }
  112. for x in values{...} do
  113. -- cat array-part
  114. for _, v in ipairs(x) do table.insert(y,v) end
  115. -- cat hash-part
  116. local lx, k = #x
  117. if lx>0 then k=next(x,lx) else k=next(x) end
  118. while k do y[k]=x[k]; k=next(x,k) end
  119. end
  120. return y
  121. end
  122. function table.deep_copy(x)
  123. local tracker = { }
  124. local function aux (x)
  125. if type(x) == "table" then
  126. local y=tracker[x]
  127. if y then return y end
  128. y = { }; tracker[x] = y
  129. setmetatable (y, getmetatable (x))
  130. for k,v in pairs(x) do y[aux(k)] = aux(v) end
  131. return y
  132. else return x end
  133. end
  134. return aux(x)
  135. end
  136. function table.override(dst, src)
  137. for k, v in pairs(src) do dst[k] = v end
  138. for i = #src+1, #dst do dst[i] = nil end
  139. return dst
  140. end
  141. function table.range(a,b,c)
  142. if not b then assert(not(c)); b=a; a=1
  143. elseif not c then c = (b>=a) and 1 or -1 end
  144. local result = { }
  145. for i=a, b, c do table.insert(result, i) end
  146. return result
  147. end
  148. -- FIXME: new_indent seems to be always nil?!
  149. -- FIXME: accumulator function should be configurable,
  150. -- so that print() doesn't need to bufferize the whole string
  151. -- before starting to print.
  152. function table.tostring(t, ...)
  153. local PRINT_HASH, HANDLE_TAG, FIX_INDENT, LINE_MAX, INITIAL_INDENT = true, true
  154. for _, x in ipairs {...} do
  155. if type(x) == "number" then
  156. if not LINE_MAX then LINE_MAX = x
  157. else INITIAL_INDENT = x end
  158. elseif x=="nohash" then PRINT_HASH = false
  159. elseif x=="notag" then HANDLE_TAG = false
  160. else
  161. local n = string['match'](x, "^indent%s*(%d*)$")
  162. if n then FIX_INDENT = tonumber(n) or 3 end
  163. end
  164. end
  165. LINE_MAX = LINE_MAX or math.huge
  166. INITIAL_INDENT = INITIAL_INDENT or 1
  167. local current_offset = 0 -- indentation level
  168. local xlen_cache = { } -- cached results for xlen()
  169. local acc_list = { } -- Generated bits of string
  170. local function acc(...) -- Accumulate a bit of string
  171. local x = table.concat{...}
  172. current_offset = current_offset + #x
  173. table.insert(acc_list, x)
  174. end
  175. local function valid_id(x)
  176. -- FIXME: we should also reject keywords; but the list of
  177. -- current keywords is not fixed in metalua...
  178. return type(x) == "string"
  179. and string['match'](x, "^[a-zA-Z_][a-zA-Z0-9_]*$")
  180. end
  181. -- Compute the number of chars it would require to display the table
  182. -- on a single line. Helps to decide whether some carriage returns are
  183. -- required. Since the size of each sub-table is required many times,
  184. -- it's cached in [xlen_cache].
  185. local xlen_type = { }
  186. local function xlen(x, nested)
  187. nested = nested or { }
  188. if x==nil then return #"nil" end
  189. --if nested[x] then return #tostring(x) end -- already done in table
  190. local len = xlen_cache[x]
  191. if len then return len end
  192. local f = xlen_type[type(x)]
  193. if not f then return #tostring(x) end
  194. len = f (x, nested)
  195. xlen_cache[x] = len
  196. return len
  197. end
  198. -- optim: no need to compute lengths if I'm not going to use them
  199. -- anyway.
  200. if LINE_MAX == math.huge then xlen = function() return 0 end end
  201. xlen_type["nil"] = function () return 3 end
  202. function xlen_type.number (x) return #tostring(x) end
  203. function xlen_type.boolean (x) return x and 4 or 5 end
  204. function xlen_type.string (x) return #string.format("%q",x) end
  205. function xlen_type.table (adt, nested)
  206. -- Circular references detection
  207. if nested [adt] then return #tostring(adt) end
  208. nested [adt] = true
  209. local has_tag = HANDLE_TAG and valid_id(adt.tag)
  210. local alen = #adt
  211. local has_arr = alen>0
  212. local has_hash = false
  213. local x = 0
  214. if PRINT_HASH then
  215. -- first pass: count hash-part
  216. for k, v in pairs(adt) do
  217. if k=="tag" and has_tag then
  218. -- this is the tag -> do nothing!
  219. elseif type(k)=="number" and k<=alen and math.fmod(k,1)==0 then
  220. -- array-part pair -> do nothing!
  221. else
  222. has_hash = true
  223. if valid_id(k) then x=x+#k
  224. else x = x + xlen (k, nested) + 2 end -- count surrounding brackets
  225. x = x + xlen (v, nested) + 5 -- count " = " and ", "
  226. end
  227. end
  228. end
  229. for i = 1, alen do x = x + xlen (adt[i], nested) + 2 end -- count ", "
  230. nested[adt] = false -- No more nested calls
  231. if not (has_tag or has_arr or has_hash) then return 3 end
  232. if has_tag then x=x+#adt.tag+1 end
  233. if not (has_arr or has_hash) then return x end
  234. if not has_hash and alen==1 and type(adt[1])~="table" then
  235. return x-2 -- substract extraneous ", "
  236. end
  237. return x+2 -- count "{ " and " }", substract extraneous ", "
  238. end
  239. -- Recursively print a (sub) table at given indentation level.
  240. -- [newline] indicates whether newlines should be inserted.
  241. local function rec (adt, nested, indent)
  242. if not FIX_INDENT then indent = current_offset end
  243. local function acc_newline()
  244. acc ("\n"); acc (string.rep (" ", indent))
  245. current_offset = indent
  246. end
  247. local x = { }
  248. x["nil"] = function() acc "nil" end
  249. function x.number() acc (tostring (adt)) end
  250. --function x.string() acc (string.format ("%q", adt)) end
  251. function x.string() acc ((string.format ("%q", adt):gsub("\\\n", "\\n"))) end
  252. function x.boolean() acc (adt and "true" or "false") end
  253. function x.table()
  254. if nested[adt] then acc(tostring(adt)); return end
  255. nested[adt] = true
  256. local has_tag = HANDLE_TAG and valid_id(adt.tag)
  257. local alen = #adt
  258. local has_arr = alen>0
  259. local has_hash = false
  260. if has_tag then acc("`"); acc(adt.tag) end
  261. -- First pass: handle hash-part
  262. if PRINT_HASH then
  263. for k, v in pairs(adt) do
  264. -- pass if the key belongs to the array-part or is the "tag" field
  265. if not (k=="tag" and HANDLE_TAG) and
  266. not (type(k)=="number" and k<=alen and math.fmod(k,1)==0) then
  267. -- Is it the first time we parse a hash pair?
  268. if not has_hash then
  269. acc "{ "
  270. if not FIX_INDENT then indent = current_offset end
  271. else acc ", " end
  272. -- Determine whether a newline is required
  273. local is_id, expected_len = valid_id(k)
  274. if is_id then expected_len = #k + xlen (v, nested) + #" = , "
  275. else expected_len = xlen (k, nested) +
  276. xlen (v, nested) + #"[] = , " end
  277. if has_hash and expected_len + current_offset > LINE_MAX
  278. then acc_newline() end
  279. -- Print the key
  280. if is_id then acc(k); acc " = "
  281. else acc "["; rec (k, nested, indent+(FIX_INDENT or 0)); acc "] = " end
  282. -- Print the value
  283. rec (v, nested, indent+(FIX_INDENT or 0))
  284. has_hash = true
  285. end
  286. end
  287. end
  288. -- Now we know whether there's a hash-part, an array-part, and a tag.
  289. -- Tag and hash-part are already printed if they're present.
  290. if not has_tag and not has_hash and not has_arr then acc "{ }";
  291. elseif has_tag and not has_hash and not has_arr then -- nothing, tag already in acc
  292. else
  293. assert (has_hash or has_arr)
  294. local no_brace = false
  295. if has_hash and has_arr then acc ", "
  296. elseif has_tag and not has_hash and alen==1 and type(adt[1])~="table" then
  297. -- No brace required; don't print "{", remember not to print "}"
  298. acc (" "); rec (adt[1], nested, indent+(FIX_INDENT or 0))
  299. no_brace = true
  300. elseif not has_hash then
  301. -- Braces required, but not opened by hash-part handler yet
  302. acc "{ "
  303. if not FIX_INDENT then indent = current_offset end
  304. end
  305. -- 2nd pass: array-part
  306. if not no_brace and has_arr then
  307. rec (adt[1], nested, indent+(FIX_INDENT or 0))
  308. for i=2, alen do
  309. acc ", ";
  310. if current_offset + xlen (adt[i], { }) > LINE_MAX
  311. then acc_newline() end
  312. rec (adt[i], nested, indent+(FIX_INDENT or 0))
  313. end
  314. end
  315. if not no_brace then acc " }" end
  316. end
  317. nested[adt] = false -- No more nested calls
  318. end
  319. local y = x[type(adt)]
  320. if y then y() else acc(tostring(adt)) end
  321. end
  322. --printf("INITIAL_INDENT = %i", INITIAL_INDENT)
  323. current_offset = INITIAL_INDENT or 0
  324. rec(t, { }, 0)
  325. return table.concat (acc_list)
  326. end
  327. function table.print(...) return print(table.tostring(...)) end
  328. return table