1
-- serialization functions for use by elua apps/modules
5
local is_array = function(t)
7
while t[i + 1] do i = i + 1 end
9
i = i - 1 if i < 0 then return false end
14
local str_escapes = setmetatable({
15
["\n"] = "\\n", ["\r"] = "\\r",
16
["\a"] = "\\a", ["\b"] = "\\b",
17
["\f"] = "\\f", ["\t"] = "\\t",
18
["\v"] = "\\v", ["\\"] = "\\\\",
19
['"' ] = '\\"', ["'" ] = "\\'"
21
__index = function(self, c) return ("\\%03d"):format(c:byte()) end
24
local escape_string = function(s)
25
-- a space optimization: decide which string quote to
26
-- use as a delimiter (the one that needs less escaping)
28
for c in s:gmatch("'") do nsq = nsq + 1 end
29
for c in s:gmatch('"') do ndq = ndq + 1 end
30
local sd = (ndq > nsq) and "'" or '"'
31
return sd .. s:gsub("[\\"..sd.."%z\001-\031]", str_escapes) .. sd
34
local function serialize_fn(v, stream, kwargs, simp, tables, indent)
39
if tv == "string" then
40
stream(escape_string(v))
41
elseif tv == "number" or tv == "boolean" then
43
elseif tv == "table" then
44
local mline = kwargs.multiline
45
local indstr = kwargs.indent
46
local asstr = kwargs.assign or "="
47
local sepstr = kwargs.table_sep or ","
48
local isepstr = kwargs.item_sep
49
local endsep = kwargs.end_sep
50
local optk = kwargs.optimize_keys
51
local arr = is_array(v)
52
local nline = arr and kwargs.narr_line or kwargs.nrec_line or 0
54
stream() -- let the stream know about an error
56
"circular table reference detected during serialization"
60
if mline then stream("\n") end
63
for k, v in (arr and ipairs or pairs)(v) do
64
if first then first = false
75
if mline and indstr and n == 0 then
76
for i = 1, indent do stream(indstr) end
79
local ret, err = serialize_fn(v, stream, kwargs, simp, tables,
81
if not ret then return ret, err end
83
if optk and type(k) == "string"
84
and k:match("^[%a_][%w_]*$") then
88
local ret, err = serialize_fn(k, stream, kwargs, simp,
90
if not ret then return ret, err end
94
local ret, err = serialize_fn(v, stream, kwargs, simp, tables,
96
if not ret then return ret, err end
101
if endsep then stream(sepstr) end
102
if mline then stream("\n") end
104
if mline and indstr then
105
for i = 2, indent do stream(indstr) end
110
return false, ("invalid value type: " .. tv)
116
multiline = false, indent = nil, assign = "=", table_sep = ",",
117
end_sep = false, optimize_keys = true
121
multiline = true, indent = " ", assign = " = ", table_sep = ",",
122
item_sep = " ", narr_line = 4, nrec_line = 2, end_sep = false,
127
Serializes the given table, returning a string containing a literal
128
representation of the table. It tries to be compact by default so it
129
avoids whitespace and newlines. Arrays and associative arrays are
130
serialized differently (for compact output).
132
Besides tables this can also serialize other Lua values. It serializes
133
them in the same way as values inside a table, returning their literal
134
representation (if serializable, otherwise just their tostring). The
135
serializer allows strings, numbers, booleans and tables.
137
Circular tables can't be serialized. The function normally returns either
138
the string output or nil + an error message (which can signalize either
139
circular references or invalid types).
141
The function allows you to pass in a "kwargs" table as the second argument.
142
It's a table of options. Those can be multiline (boolean, false by default,
143
pretty much pretty-printing), indent (string, nil by default, specifies
144
how an indent level looks), assign (string, "=" by default, specifies how
145
an assignment between a key and a value looks), table_sep (table separator,
146
by default ",", can also be ";" for tables, separates items in all cases),
147
item_sep (item separator, string, nil by default, comes after table_sep
148
but only if it isn't followed by a newline), narr_line (number, 0 by
149
default, how many array elements to fit on a line), nrec_line (same,
150
just for key-value pairs), end_sep (boolean, false by default, makes
151
the serializer put table_sep after every item including the last one),
152
optimize_keys (boolean, true by default, optimizes string keys like
153
that it doesn't use string literals for keys that can be expressed
156
If kwargs is nil or false, the values above are used. If kwargs is a
157
boolean value true, pretty-printing defaults are used (multiline is
158
true, indent is 4 spaces, assign is " = ", table_sep is ",", item_sep
159
is one space, narr_line is 4, nrec_line is 2, end_sep is false,
160
optimize_keys is true).
162
A third argument, "stream" can be passed. As a table is serialized
163
by pieces, "stream" is called each time a new piece is saved. It's
164
useful for example for file I/O. When a custom stream is supplied,
165
the function doesn't return a string, instead it returns true
166
or false depending on whether it succeeded and the error message
169
And finally there is the fourth argument, "simplifier". It's a
170
function that takes a value and "simplifies" it (returns another
171
value it should be replaced by). By default nothing is simplified
174
This function is externally available as "table_serialize".
176
M.serialize = function(val, kwargs, stream, simplifier)
177
if kwargs == true then
179
elseif not kwargs then
182
if kwargs.optimize_keys == nil then
183
kwargs.optimize_keys = true
187
return serialize_fn(val, stream, kwargs, simplifier, {}, 1)
190
local ret, err = serialize_fn(val, function(out)
191
t[#t + 1] = out end, kwargs, simplifier, {}, 1)
195
return table.concat(t)
200
local lex_get = function(ls)
203
if not c then break end
204
ls.tname, ls.tval = nil, nil
205
if c == "\n" or c == "\r" then
208
if (c == "\n" or c == "\r") and c ~= prev then
212
ls.linenum = ls.linenum + 1
213
elseif c == " " or c == "\t" or c == "\f" or c == "\v" then
215
elseif c == "." or c:byte() >= 48 and c:byte() <= 57 then
216
local buf = { ls.curr }
218
while ls.curr and ls.curr:match("[epxEPX0-9.+-]") do
219
buf[#buf + 1] = ls.curr
222
local str = table.concat(buf)
223
local num = tonumber(str)
224
if not num then error(("%d: malformed number near '%s'")
225
:format(ls.linenum, str), 0) end
226
ls.tname, ls.tval = "<number>", num
228
elseif c == '"' or c == "'" then
232
while ls.curr ~= d do
235
error(("%d: unfinished string near '<eos>'")
236
:format(ls.linenum), 0)
237
elseif c == "\n" or c == "\r" then
238
error(("%d: unfinished string near '<string>'")
239
:format(ls.linenum), 0)
240
-- not complete escape sequence handling: handles only these
241
-- that are or can be in the serialized output
242
elseif c == "\\" then
245
buf[#buf + 1] = "\a" ls.curr = ls.rdr()
247
buf[#buf + 1] = "\b" ls.curr = ls.rdr()
249
buf[#buf + 1] = "\f" ls.curr = ls.rdr()
251
buf[#buf + 1] = "\n" ls.curr = ls.rdr()
253
buf[#buf + 1] = "\r" ls.curr = ls.rdr()
255
buf[#buf + 1] = "\t" ls.curr = ls.rdr()
257
buf[#buf + 1] = "\v" ls.curr = ls.rdr()
258
elseif c == "\\" or c == '"' or c == "'" then
262
error(("%d: unfinished string near '<eos>'")
263
:format(ls.linenum), 0)
265
if not c:match("%d") then
266
error(("%d: invalid escape sequence")
267
:format(ls.linenum), 0)
271
if c:match("%d") then
274
if c:match("%d") then
280
buf[#buf + 1] = table.concat(dbuf):char()
287
ls.curr = ls.rdr() -- skip delim
288
ls.tname, ls.tval = "<string>", table.concat(buf)
290
elseif c:match("[%a_]") then
293
while ls.curr and ls.curr:match("[%w_]") do
294
buf[#buf + 1] = ls.curr
297
local str = table.concat(buf)
298
if str == "true" or str == "false" or str == "nil" then
299
ls.tname, ls.tval = str, nil
302
ls.tname, ls.tval = "<name>", str
307
ls.tname, ls.tval = c, nil
313
local function assert_tok(ls, tok, ...)
314
if not tok then return nil end
315
if ls.tname ~= tok then
316
error(("%d: unexpected symbol near '%s'"):format(ls.linenum,
323
local function parse(ls)
325
if tok == "<string>" or tok == "<number>" then
329
elseif tok == "true" then lex_get(ls) return true
330
elseif tok == "false" then lex_get(ls) return false
331
elseif tok == "nil" then lex_get(ls) return nil
335
if ls.tname == "}" then
340
if ls.tname == "<name>" then
345
elseif ls.tname == "[" then
347
local key = parse(ls)
348
assert_tok(ls, "]", "=")
351
tbl[#tbl + 1] = parse(ls)
353
until (ls.tname ~= "," and ls.tname ~= ";") or not lex_get(ls)
360
Takes a previously serialized table and converts it back to the original.
361
Uses a simple tokenizer and a recursive descent parser to build the result,
362
so it's safe (doesn't evaluate anything). The input can also be a callable
363
value that return the next character each call.
364
External as "table_deserialize". This returns the deserialized value on
365
success and nil + the error message on failure.
367
M.deserialize = function(s)
368
local stream = (type(s) == "string") and s:gmatch(".") or s
369
local ls = { curr = stream(), rdr = stream, linenum = 1 }
370
local r, v = pcall(lex_get, ls)
371
if not r then return nil, v end
372
r, v = pcall(parse, ls)
373
if not r then return nil, v end