2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 -- Copyright (C) 2018 Kim Alvefur
6 -- This project is MIT/X11 licensed. Please see the
7 -- COPYING file in the source package for more information.
10 local getmetatable
= getmetatable
;
11 local next, type = next, type;
12 local s_format
= string.format;
13 local s_gsub
= string.gsub;
14 local s_rep
= string.rep
;
15 local s_char
= string.char
;
16 local s_match
= string.match
;
17 local t_concat
= table.concat
;
19 local to_hex
= require
"util.hex".to
;
22 local envload
= require
"util.envload".envload
;
24 local pos_inf
, neg_inf
= math
.huge
, -math
.huge
;
25 local m_type
= math
.type or function (n
)
26 return n
% 1 == 0 and n
<= 9007199254740992 and n
>= -9007199254740992 and "integer" or "float";
29 local function rawpairs(t
)
33 local function fatal_error(obj
, why
)
34 error("Can't serialize "..type(obj
) .. (why
and ": ".. why
or ""));
37 local function nonfatal_fallback(x
, why
)
38 return s_format("{__type=%q,__error=%q}", type(x
), why
or "fail");
41 local string_escapes
= {
42 ['\a'] = [[\a]]; ['\b'] = [[\b]];
43 ['\f'] = [[\f]]; ['\n'] = [[\n]];
44 ['\r'] = [[\r]]; ['\t'] = [[\t]];
45 ['\v'] = [[\v]]; ['\\'] = [[\\]];
46 ['\"'] = [[\"]]; ['\''] = [[\']];
51 if not string_escapes
[c
] then
52 string_escapes
[c
] = s_format("\\%03d", i
);
56 local default_keywords
= {
57 ["do"] = true; ["and"] = true; ["else"] = true; ["break"] = true;
58 ["if"] = true; ["end"] = true; ["goto"] = true; ["false"] = true;
59 ["in"] = true; ["for"] = true; ["then"] = true; ["local"] = true;
60 ["or"] = true; ["nil"] = true; ["true"] = true; ["until"] = true;
61 ["elseif"] = true; ["function"] = true; ["not"] = true;
62 ["repeat"] = true; ["return"] = true; ["while"] = true;
65 local function new(opt
)
66 if type(opt
) ~= "table" then
67 opt
= { preset
= opt
};
79 if opt
.preset
== "debug" then
80 opt
.preset
= "oneline";
83 opt
.fallback
= nonfatal_fallback
;
86 if opt
.preset
== "oneline" then
87 opt
.indentwith
= opt
.indentwith
or "";
88 opt
.itemstart
= opt
.itemstart
or " ";
89 opt
.itemlast
= opt
.itemlast
or "";
90 opt
.tend
= opt
.tend
or " }";
91 elseif opt
.preset
== "compact" then
92 opt
.indentwith
= opt
.indentwith
or "";
93 opt
.itemstart
= opt
.itemstart
or "";
94 opt
.itemlast
= opt
.itemlast
or "";
95 opt
.equals
= opt
.equals
or "=";
99 local fallback
= opt
.fallback
or opt
.fatal
== false and nonfatal_fallback
or fatal_error
;
101 local function ser(v
)
102 return (types
[type(v
)] or fallback
)(v
);
105 local keywords
= opt
.keywords
or default_keywords
;
108 local indentwith
= opt
.indentwith
or "\t";
109 local itemstart
= opt
.itemstart
or "\n";
110 local itemsep
= opt
.itemsep
or ";";
111 local itemlast
= opt
.itemlast
or ";\n";
112 local tstart
= opt
.tstart
or "{";
113 local tend
= opt
.tend
or "}";
114 local kstart
= opt
.kstart
or "[";
115 local kend
= opt
.kend
or "]";
116 local equals
= opt
.equals
or " = ";
117 local unquoted
= opt
.unquoted
== true and "^[%a_][%w_]*$" or opt
.unquoted
;
119 local freeze
= opt
.freeze
;
120 local maxdepth
= opt
.maxdepth
or 127;
121 local multirefs
= opt
.multiref
;
122 local table_pairs
= opt
.table_iterator
or rawpairs
;
124 -- serialize one table, recursively
125 -- t - table being serialized
126 -- o - array where tokens are added, concatenate to get final result
127 -- - also used to detect cycles
128 -- l - position in o of where to insert next token
129 -- d - depth, used for indentation
130 local function serialize_table(t
, o
, l
, d
)
132 o
[l
], l
= fallback(t
, "table has multiple references"), l
+ 1;
134 elseif d
> maxdepth
then
135 o
[l
], l
= fallback(t
, "max table depth reached"), l
+ 1;
139 -- Keep track of table loops
140 local ot
= t
; -- reference pre-freeze
144 if freeze
== true then
145 -- opportunity to do pre-serialization
146 local mt
= getmetatable(t
);
147 if type(mt
) == "table" then
148 local tag = mt
.__name
;
149 local fr
= mt
.__freeze
;
151 if type(fr
) == "function" then
153 if type(tag) == "string" then
154 o
[l
], l
= tag, l
+ 1;
160 o
[l
], l
= tstart
, l
+ 1;
161 local indent
= s_rep(indentwith
, d
);
164 local had_items
= false;
165 for k
,v
in table_pairs(t
) do
167 o
[l
], l
= itemstart
, l
+ 1;
168 o
[l
], l
= indent
, l
+ 1;
169 ktyp
, vtyp
= type(k
), type(v
);
171 -- next index in array part
172 -- assuming that these are found in order
174 elseif unquoted
and ktyp
== "string" and
175 not keywords
[k
] and s_match(k
, unquoted
) then
178 o
[l
], l
= equals
, l
+ 1;
181 o
[l
], l
= kstart
, l
+ 1;
182 if ktyp
== "table" then
183 l
= serialize_table(k
, o
, l
, d
+1);
185 o
[l
], l
= ser(k
), l
+ 1;
188 o
[l
], o
[l
+1], l
= kend
, equals
, l
+ 2;
192 if vtyp
== "table" then
193 l
= serialize_table(v
, o
, l
, d
+1);
195 o
[l
], l
= ser(v
), l
+ 1;
197 o
[l
], l
= itemsep
, l
+ 1;
201 o
[l
], l
= s_rep(indentwith
, d
-1), l
+ 1;
203 o
[l
], l
= tend
, l
+1;
213 function types
.table(t
)
215 serialize_table(t
, o
, 1, 1);
219 local function serialize_string(s
)
220 return '"' .. s_gsub(s
, "[%z\1-\31\"\'\\\127-\255]", string_escapes
) .. '"';
223 if type(hex
) == "string" then
224 function types
.string(s
)
225 local esc
= serialize_string(s
);
226 if #esc
> (#s
*2+2+#hex
) then
227 return hex
.. '"' .. to_hex(s
) .. '"';
232 types
.string = serialize_string
;
235 function types
.number(t
)
236 if m_type(t
) == "integer" then
237 return s_format("%d", t
);
238 elseif t
== pos_inf
then
240 elseif t
== neg_inf
then
245 return s_format("%.18g", t
);
248 -- Are these faster than tostring?
249 types
["nil"] = function()
253 function types
.boolean(t
)
254 return t
and "true" or "false";
260 local function deserialize(str
)
261 if type(str
) ~= "string" then return nil; end
262 str
= "return "..str
;
263 local f
, err
= envload(str
, "=serialized data", {});
264 if not f
then return nil, err
; end
265 local success
, ret
= pcall(f
);
266 if not success
then return nil, ret
; end
270 local default
= new();
273 serialize
= function (x
, opt
)
280 deserialize
= deserialize
;