1+ -- @noindex
2+
3+ local n , v = " serpent" , " 0.303" -- (C) 2012-18 Paul Kulchenko; MIT License
4+ local c , d = " Paul Kulchenko" , " Lua serializer and pretty printer"
5+ local snum = {[tostring (1 / 0 )]= ' 1/0 --[[math.huge]]' ,[tostring (- 1 / 0 )]= ' -1/0 --[[-math.huge]]' ,[tostring (0 / 0 )]= ' 0/0' }
6+ local badtype = {thread = true , userdata = true , cdata = true }
7+ local getmetatable = debug and debug.getmetatable or getmetatable
8+ local pairs = function (t ) return next , t end -- avoid using __pairs in Lua 5.2+
9+ local keyword , globals , G = {}, {}, (_G or _ENV )
10+ for _ ,k in ipairs ({' and' , ' break' , ' do' , ' else' , ' elseif' , ' end' , ' false' ,
11+ ' for' , ' function' , ' goto' , ' if' , ' in' , ' local' , ' nil' , ' not' , ' or' , ' repeat' ,
12+ ' return' , ' then' , ' true' , ' until' , ' while' }) do keyword [k ] = true end
13+ for k ,v in pairs (G ) do globals [v ] = k end -- build func to name mapping
14+ for _ ,g in ipairs ({' coroutine' , ' debug' , ' io' , ' math' , ' string' , ' table' , ' os' }) do
15+ for k ,v in pairs (type (G [g ]) == ' table' and G [g ] or {}) do globals [v ] = g .. ' .' .. k end end
16+
17+ local function s (t , opts )
18+ local name , indent , fatal , maxnum = opts .name , opts .indent , opts .fatal , opts .maxnum
19+ local sparse , custom , huge = opts .sparse , opts .custom , not opts .nohuge
20+ local space , maxl = (opts .compact and ' ' or ' ' ), (opts .maxlevel or math.huge )
21+ local maxlen , metatostring = tonumber (opts .maxlength ), opts .metatostring
22+ local iname , comm = ' _' .. (name or ' ' ), opts .comment and (tonumber (opts .comment ) or math.huge )
23+ local numformat = opts .numformat or " %.17g"
24+ local seen , sref , syms , symn = {}, {' local ' .. iname .. ' ={}' }, {}, 0
25+ local function gensym (val ) return ' _' .. (tostring (tostring (val )):gsub (" [^%w]" ," " ):gsub (" (%d%w+)" ,
26+ -- tostring(val) is needed because __tostring may return a non-string value
27+ function (s ) if not syms [s ] then symn = symn + 1 ; syms [s ] = symn end return tostring (syms [s ]) end )) end
28+ local function safestr (s ) return type (s ) == " number" and (huge and snum [tostring (s )] or numformat :format (s ))
29+ or type (s ) ~= " string" and tostring (s ) -- escape NEWLINE/010 and EOF/026
30+ or (" %q" ):format (s ):gsub (" \010 " ," n" ):gsub (" \026 " ," \\ 026" ) end
31+ -- handle radix changes in some locales
32+ if opts .fixradix and (" .1f" ):format (1.2 ) ~= " 1.2" then
33+ local origsafestr = safestr
34+ safestr = function (s ) return type (s ) == " number"
35+ and (nohuge and snum [tostring (s )] or numformat :format (s ):gsub (" ," ," ." )) or origsafestr (s )
36+ end
37+ end
38+ local function comment (s ,l ) return comm and (l or 0 ) < comm and ' --[[' .. select (2 , pcall (tostring , s )).. ' ]]' or ' ' end
39+ local function globerr (s ,l ) return globals [s ] and globals [s ].. comment (s ,l ) or not fatal
40+ and safestr (select (2 , pcall (tostring , s ))) or error (" Can't serialize " .. tostring (s )) end
41+ local function safename (path , name ) -- generates foo.bar, foo[3], or foo['b a r']
42+ local n = name == nil and ' ' or name
43+ local plain = type (n ) == " string" and n :match (" ^[%l%u_][%w_]*$" ) and not keyword [n ]
44+ local safe = plain and n or ' [' .. safestr (n ).. ' ]'
45+ return (path or ' ' ).. (plain and path and ' .' or ' ' ).. safe , safe end
46+ local alphanumsort = type (opts .sortkeys ) == ' function' and opts .sortkeys or function (k , o , n ) -- k=keys, o=originaltable, n=padding
47+ local maxn , to = tonumber (n ) or 12 , {number = ' a' , string = ' b' }
48+ local function padnum (d ) return (" %0" .. tostring (maxn ).. " d" ):format (tonumber (d )) end
49+ table.sort (k , function (a ,b )
50+ -- sort numeric keys first: k[key] is not nil for numerical keys
51+ return (k [a ] ~= nil and 0 or to [type (a )] or ' z' ).. (tostring (a ):gsub (" %d+" ,padnum ))
52+ < (k [b ] ~= nil and 0 or to [type (b )] or ' z' ).. (tostring (b ):gsub (" %d+" ,padnum )) end ) end
53+ local function val2str (t , name , indent , insref , path , plainindex , level )
54+ local ttype , level , mt = type (t ), (level or 0 ), getmetatable (t )
55+ local spath , sname = safename (path , name )
56+ local tag = plainindex and
57+ ((type (name ) == " number" ) and ' ' or name .. space .. ' =' .. space ) or
58+ (name ~= nil and sname .. space .. ' =' .. space or ' ' )
59+ if seen [t ] then -- already seen this element
60+ sref [# sref + 1 ] = spath .. space .. ' =' .. space .. seen [t ]
61+ return tag .. ' nil' .. comment (' ref' , level )
62+ end
63+ -- protect from those cases where __tostring may fail
64+ if type (mt ) == ' table' and metatostring ~= false then
65+ local to , tr = pcall (function () return mt .__tostring (t ) end )
66+ local so , sr = pcall (function () return mt .__serialize (t ) end )
67+ if (to or so ) then -- knows how to serialize itself
68+ seen [t ] = insref or spath
69+ t = so and sr or tr
70+ ttype = type (t )
71+ end -- new value falls through to be serialized
72+ end
73+ if ttype == " table" then
74+ if level >= maxl then return tag .. ' {}' .. comment (' maxlvl' , level ) end
75+ seen [t ] = insref or spath
76+ if next (t ) == nil then return tag .. ' {}' .. comment (t , level ) end -- table empty
77+ if maxlen and maxlen < 0 then return tag .. ' {}' .. comment (' maxlen' , level ) end
78+ local maxn , o , out = math.min (# t , maxnum or # t ), {}, {}
79+ for key = 1 , maxn do o [key ] = key end
80+ if not maxnum or # o < maxnum then
81+ local n = # o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables
82+ for key in pairs (t ) do
83+ if o [key ] ~= key then n = n + 1 ; o [n ] = key end
84+ end
85+ end
86+ if maxnum and # o > maxnum then o [maxnum + 1 ] = nil end
87+ if opts .sortkeys and # o > maxn then alphanumsort (o , t , opts .sortkeys ) end
88+ local sparse = sparse and # o > maxn -- disable sparsness if only numeric keys (shorter output)
89+ for n , key in ipairs (o ) do
90+ local value , ktype , plainindex = t [key ], type (key ), n <= maxn and not sparse
91+ if opts .valignore and opts .valignore [value ] -- skip ignored values; do nothing
92+ or opts .keyallow and not opts .keyallow [key ]
93+ or opts .keyignore and opts .keyignore [key ]
94+ or opts .valtypeignore and opts .valtypeignore [type (value )] -- skipping ignored value types
95+ or sparse and value == nil then -- skipping nils; do nothing
96+ elseif ktype == ' table' or ktype == ' function' or badtype [ktype ] then
97+ if not seen [key ] and not globals [key ] then
98+ sref [# sref + 1 ] = ' placeholder'
99+ local sname = safename (iname , gensym (key )) -- iname is table for local variables
100+ sref [# sref ] = val2str (key ,sname ,indent ,sname ,iname ,true )
101+ end
102+ sref [# sref + 1 ] = ' placeholder'
103+ local path = seen [t ].. ' [' .. tostring (seen [key ] or globals [key ] or gensym (key )).. ' ]'
104+ sref [# sref ] = path .. space .. ' =' .. space .. tostring (seen [value ] or val2str (value ,nil ,indent ,path ))
105+ else
106+ out [# out + 1 ] = val2str (value ,key ,indent ,nil ,seen [t ],plainindex ,level + 1 )
107+ if maxlen then
108+ maxlen = maxlen - # out [# out ]
109+ if maxlen < 0 then break end
110+ end
111+ end
112+ end
113+ local prefix = string.rep (indent or ' ' , level )
114+ local head = indent and ' {\n ' .. prefix .. indent or ' {'
115+ local body = table.concat (out , ' ,' .. (indent and ' \n ' .. prefix .. indent or space ))
116+ local tail = indent and " \n " .. prefix .. ' }' or ' }'
117+ return (custom and custom (tag ,head ,body ,tail ,level ) or tag .. head .. body .. tail ).. comment (t , level )
118+ elseif badtype [ttype ] then
119+ seen [t ] = insref or spath
120+ return tag .. globerr (t , level )
121+ elseif ttype == ' function' then
122+ seen [t ] = insref or spath
123+ if opts .nocode then return tag .. " function() --[[..skipped..]] end" .. comment (t , level ) end
124+ local ok , res = pcall (string.dump , t )
125+ local func = ok and " ((loadstring or load)(" .. safestr (res ).. " ,'@serialized'))" .. comment (t , level )
126+ return tag .. (func or globerr (t , level ))
127+ else return tag .. safestr (t ) end -- handle all other types
128+ end
129+ local sepr = indent and " \n " or " ;" .. space
130+ local body = val2str (t , name , indent ) -- this call also populates sref
131+ local tail = # sref > 1 and table.concat (sref , sepr ).. sepr or ' '
132+ local warn = opts .comment and # sref > 1 and space .. " --[[incomplete output with shared/self-references skipped]]" or ' '
133+ return not name and body .. warn or " do local " .. body .. sepr .. tail .. " return " .. name .. sepr .. " end"
134+ end
135+
136+ local function deserialize (data , opts )
137+ local env = (opts and opts .safe == false ) and G
138+ or setmetatable ({}, {
139+ __index = function (t ,k ) return t end ,
140+ __call = function (t ,...) error (" cannot call functions" ) end
141+ })
142+ local f , res = (loadstring or load )(' return ' .. data , nil , nil , env )
143+ if not f then f , res = (loadstring or load )(data , nil , nil , env ) end
144+ if not f then return f , res end
145+ if setfenv then setfenv (f , env ) end
146+ return pcall (f )
147+ end
148+
149+ local function merge (a , b ) if b then for k ,v in pairs (b ) do a [k ] = v end end ; return a ; end
150+ return { _NAME = n , _COPYRIGHT = c , _DESCRIPTION = d , _VERSION = v , serialize = s ,
151+ load = deserialize ,
152+ dump = function (a , opts ) return s (a , merge ({name = ' _' , compact = true , sparse = true }, opts )) end ,
153+ line = function (a , opts ) return s (a , merge ({sortkeys = true , comment = true }, opts )) end ,
154+ block = function (a , opts ) return s (a , merge ({indent = ' ' , sortkeys = true , comment = true }, opts )) end }
0 commit comments