PageRenderTime 35ms CodeModel.GetById 18ms app.highlight 13ms RepoModel.GetById 1ms app.codeStats 0ms

/metalualib/metalua/table2.lua

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