]> git.lizzy.rs Git - metalua.git/blob - src/lib/serialize.lua
7bbe092ab793f2d26fbce055024ef035b34734dc
[metalua.git] / src / lib / serialize.lua
1 --------------------------------------------------------------------------------
2 -- Serialize an object into a source code string. This string, when passed as
3 -- an argument to loadstring()(), returns an object structurally identical
4 -- to the original one. The following are currently supported:
5 -- * strings, numbers, booleans, nil
6 -- * functions without upvalues
7 -- * tables thereof. Tables cna have shared part, but can't be recursive yet.
8 -- Caveat: metatables and environments aren't saved.
9 --------------------------------------------------------------------------------
10
11 local no_identity = table.transpose { 'number', 'boolean', 'string', 'nil' }
12
13 function serialize (x)
14
15    local gensym_max =  0  -- index of the gensym() symbol generator
16    local seen_once  = { } -- element->true set of elements seen exactly once in the table
17    local multiple   = { } -- element->varname set of elements seen more than once
18    local nested     = { } -- transient, set of elements currently being traversed
19
20    local function gensym()
21       gensym_max = gensym_max + 1 ;  return gensym_max
22    end
23
24    -----------------------------------------------------------------------------
25    -- First pass, list the tables and functions which appear more than once in x
26    -----------------------------------------------------------------------------
27    local function mark_multiple_occurences (x)
28       if no_identity [type(x)] then return end
29       if     seen_once [x]     then seen_once [x], multiple [x] = nil, true
30       elseif multiple  [x]     then -- pass
31       else   seen_once [x] = true end
32       
33       if type (x) == 'table' then
34          nested [x] = true
35          for k, v in pairs (x) do
36             if nested [k] then error "Can't serialize recursive tables yet" end
37             mark_multiple_occurences (k)
38             mark_multiple_occurences (v)
39          end
40          nested [x] = nil
41       end
42    end
43
44    local dumped    = { } -- multiply occuring values already dumped in localdefs
45    local localdefs = { } -- already dumped local definitions as source code lines
46
47    -----------------------------------------------------------------------------
48    -- Second pass, dump the object; subparts occuring multiple times are dumped
49    -- in local variables which can be referenced multiple times;
50    -- care is taken to dump locla vars in asensible order.
51    -----------------------------------------------------------------------------
52    local function dump_val(x)
53       local  t = type(x)
54       if     x==nil        then return 'nil'
55       elseif t=="number"   then return tostring(x)
56       elseif t=="string"   then return string.format("%q", x)
57       elseif t=="boolean"  then return x and "true" or "false"
58       elseif t=="function" then
59          return string.format ("loadstring(%q,'@serialized')", string.dump (x))
60       elseif t=="table" then
61          local acc = { }
62          --------------------------------------------------------------------
63          -- if x occurs multiple times, dump the local var rather than the
64          -- value. If it's the first time it's dumped, also dump the content
65          -- in localdefs.
66          --------------------------------------------------------------------            
67          local function check_multiple (x)
68             if not multiple[x] then return dump_val (x) end
69             local var = dumped [x]
70             if var then return "_[" .. var .. "]" end
71             local val = dump_val(x)
72             var = gensym()
73             table.insert(localdefs, "_["..var.."]="..val)
74             dumped [x] = var
75             return "_[" .. var .. "]"
76          end
77
78          local idx_dumped = { }
79          for i, v in ipairs(x) do
80             table.insert (acc, check_multiple(v))
81             idx_dumped[i] = true
82          end
83          for k, v in pairs(x) do
84             if not idx_dumped[k] then
85                table.insert (acc, "[" .. check_multiple(k) .. "] = " .. check_multiple(v))
86             end
87          end
88          return "{ "..table.concat(acc,", ").." }"
89       else
90          error ("Can't serialize data of type "..t)
91       end
92    end
93           
94    mark_multiple_occurences (x)
95    local toplevel = dump_val (x)
96    if next (localdefs) then
97       return "local _={ }\n" ..
98          table.concat (localdefs, "\n") .. 
99          "\nreturn " .. toplevel
100    else
101       return "return " .. toplevel
102    end
103 end