1 --------------------------------------------------------------------------------
3 -- Summary: Table-to-source serializer
4 --------------------------------------------------------------------------------
6 -- Copyright (c) 2008-2009, Fabien Fleutot <metalua@gmail.com>.
8 -- This software is released under the MIT Licence, see licence.txt
11 --------------------------------------------------------------------------------
13 -- Serialize an object into a source code string. This string, when passed as
14 -- an argument to loadstring()(), returns an object structurally identical
15 -- to the original one.
17 -- The following are supported:
19 -- * strings, numbers, booleans, nil
21 -- * functions without upvalues
23 -- * tables thereof. There is no restriction on keys; recursive and shared
24 -- sub-tables are handled correctly.
26 -- Caveat: metatables and environments aren't saved; this might or might not
28 --------------------------------------------------------------------------------
30 local no_identity = { number=1, boolean=1, string=1, ['nil']=1 }
32 function serialize (x)
34 local gensym_max = 0 -- index of the gensym() symbol generator
35 local seen_once = { } -- element->true set of elements seen exactly once in the table
36 local multiple = { } -- element->varname set of elements seen more than once
37 local nested = { } -- transient, set of elements currently being traversed
38 local nest_points = { }
39 local nest_patches = { }
41 -- Generate fresh indexes to store new sub-tables:
42 local function gensym()
43 gensym_max = gensym_max + 1 ; return gensym_max
46 -----------------------------------------------------------------------------
47 -- `nest_points' are places where a (recursive) table appears within
48 -- itself, directly or not. for instance, all of these chunks
49 -- create nest points in table `x':
51 -- "x = { }; x[x] = 1"
52 -- "x = { }; x[1] = x"
53 -- "x = { }; x[1] = { y = { x } }".
55 -- To handle those, two tables are created by `mark_nest_point()':
57 -- * `nest_points [parent]' associates all keys and values in table
58 -- parent which create a nest_point with boolean `true'
60 -- * `nest_patches' contains a list of `{ parent, key, value }'
61 -- tuples creating a nest point. They're all dumped after all the
62 -- other table operations have been performed.
64 -- `mark_nest_point (p, k, v)' fills tables `nest_points' and
65 -- `nest_patches' with informations required to remember that
66 -- key/value `(k,v)' creates a nest point in parent table `p'. It
67 -- also marks `p' as occuring multiple times, since several
68 -- references to it will be required in order to patch the nest
70 -----------------------------------------------------------------------------
71 local function mark_nest_point (parent, k, v)
72 local nk, nv = nested[k], nested[v]
73 assert (not nk or seen_once[k] or multiple[k])
74 assert (not nv or seen_once[v] or multiple[v])
75 local mode = (nk and nv and "kv") or (nk and "k") or ("v")
76 local parent_np = nest_points [parent]
78 if not parent_np then parent_np = { }; nest_points [parent] = parent_np end
79 parent_np [k], parent_np [v] = nk, nv
80 table.insert (nest_patches, { parent, k, v })
81 seen_once [parent], multiple [parent] = nil, true
84 -----------------------------------------------------------------------------
85 -- 1st pass, list the tables and functions which appear more than once in `x'
86 -----------------------------------------------------------------------------
87 local function mark_multiple_occurences (x)
88 if no_identity [type(x)] then return end
89 if seen_once [x] then seen_once [x], multiple [x] = nil, true
90 elseif multiple [x] then -- pass
91 else seen_once [x] = true end
93 if type (x) == 'table' then
95 for k, v in pairs (x) do
96 if nested[k] or nested[v] then mark_nest_point (x, k, v) else
97 mark_multiple_occurences (k)
98 mark_multiple_occurences (v)
105 local dumped = { } -- multiply occuring values already dumped in localdefs
106 local localdefs = { } -- already dumped local definitions as source code lines
109 -- mutually recursive functions:
110 local dump_val, dump_or_ref_val
112 ------------------------------------------------------------------------------
113 -- if `x' occurs multiple times, dump the local var rather than the
114 -- value. If it's the first time it's dumped, also dump the content
116 ------------------------------------------------------------------------------
117 function dump_or_ref_val (x)
118 if nested[x] then return 'false' end -- placeholder for recursive reference
119 if not multiple[x] then return dump_val (x) end
120 local var = dumped [x]
121 if var then return "_[" .. var .. "]" end -- already referenced
122 local val = dump_val(x) -- first occurence, create and register reference
124 table.insert(localdefs, "_["..var.."]="..val)
126 return "_[" .. var .. "]"
129 -----------------------------------------------------------------------------
130 -- 2nd pass, dump the object; subparts occuring multiple times are dumped
131 -- in local variables, which can then be referenced multiple times;
132 -- care is taken to dump local vars in an order which repect dependencies.
133 -----------------------------------------------------------------------------
136 if x==nil then return 'nil'
137 elseif t=="number" then return tostring(x)
138 elseif t=="string" then return string.format("%q", x)
139 elseif t=="boolean" then return x and "true" or "false"
140 elseif t=="function" then
141 return string.format ("loadstring(%q,'@serialized')", string.dump (x))
142 elseif t=="table" then
145 local idx_dumped = { }
146 local np = nest_points [x]
147 for i, v in ipairs(x) do
149 table.insert (acc, 'false') -- placeholder
151 table.insert (acc, dump_or_ref_val(v))
155 for k, v in pairs(x) do
156 if np and (np[k] or np[v]) then
157 --check_multiple(k); check_multiple(v) -- force dumps in localdefs
158 elseif not idx_dumped[k] then
159 table.insert (acc, "[" .. dump_or_ref_val(k) .. "] = " .. dump_or_ref_val(v))
162 return "{ "..table.concat(acc,", ").." }"
164 error ("Can't serialize data of type "..t)
168 -- Patch the recursive table entries:
169 local function dump_nest_patches()
170 for _, entry in ipairs(nest_patches) do
171 local p, k, v = unpack (entry)
173 local set = dump_or_ref_val (p) .. "[" .. dump_or_ref_val (k) .. "] = " ..
174 dump_or_ref_val (v) .. " -- rec "
175 table.insert (localdefs, set)
179 mark_multiple_occurences (x)
180 local toplevel = dump_or_ref_val (x)
183 if next (localdefs) then
184 -- Dump local vars containing shared or recursive parts,
185 -- then the main table using them.
186 return "local _={ }\n" ..
187 table.concat (localdefs, "\n") ..
188 "\nreturn " .. toplevel
190 -- No shared part, straightforward dump:
191 return "return " .. toplevel