1+ --[[
2+ GitHub: https://github.com/pkulchenko/serpent/tree/139fc18263bc5ffecc1729147891f1eb383046bf
3+
4+ Serpent source is released under the MIT License
5+
6+ Copyright (c) 2012-2018 Paul Kulchenko (paul@kulchenko.com)
7+
8+ Permission is hereby granted, free of charge, to any person obtaining a copy
9+ of this software and associated documentation files (the "Software"), to deal
10+ in the Software without restriction, including without limitation the rights
11+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+ copies of the Software, and to permit persons to whom the Software is
13+ furnished to do so, subject to the following conditions:
14+
15+ The above copyright notice and this permission notice shall be included in
16+ all copies or substantial portions of the Software.
17+
18+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+ THE SOFTWARE.
25+ ]]
26+
27+ local n , v = " serpent" , " 0.303" -- (C) 2012-18 Paul Kulchenko; MIT License
28+ local c , d = " Paul Kulchenko" , " Lua serializer and pretty printer"
29+ local snum = {[tostring (1 / 0 )]= ' 1/0 --[[math.huge]]' ,[tostring (- 1 / 0 )]= ' -1/0 --[[-math.huge]]' ,[tostring (0 / 0 )]= ' 0/0' }
30+ local badtype = {thread = true , userdata = true , cdata = true }
31+ local getmetatable = debug and debug.getmetatable or getmetatable
32+ local pairs = function (t ) return next , t end -- avoid using __pairs in Lua 5.2+
33+ local keyword , globals , G = {}, {}, (_G or _ENV )
34+ for _ ,k in ipairs ({' and' , ' break' , ' do' , ' else' , ' elseif' , ' end' , ' false' ,
35+ ' for' , ' function' , ' goto' , ' if' , ' in' , ' local' , ' nil' , ' not' , ' or' , ' repeat' ,
36+ ' return' , ' then' , ' true' , ' until' , ' while' }) do keyword [k ] = true end
37+ for k ,v in pairs (G ) do globals [v ] = k end -- build func to name mapping
38+ for _ ,g in ipairs ({' coroutine' , ' debug' , ' io' , ' math' , ' string' , ' table' , ' os' }) do
39+ for k ,v in pairs (type (G [g ]) == ' table' and G [g ] or {}) do globals [v ] = g .. ' .' .. k end end
40+
41+ local function s (t , opts )
42+ local name , indent , fatal , maxnum = opts .name , opts .indent , opts .fatal , opts .maxnum
43+ local sparse , custom , huge = opts .sparse , opts .custom , not opts .nohuge
44+ local space , maxl = (opts .compact and ' ' or ' ' ), (opts .maxlevel or math.huge )
45+ local maxlen , metatostring = tonumber (opts .maxlength ), opts .metatostring
46+ local iname , comm = ' _' .. (name or ' ' ), opts .comment and (tonumber (opts .comment ) or math.huge )
47+ local numformat = opts .numformat or " %.17g"
48+ local seen , sref , syms , symn = {}, {' local ' .. iname .. ' ={}' }, {}, 0
49+ local function gensym (val ) return ' _' .. (tostring (tostring (val )):gsub (" [^%w]" ," " ):gsub (" (%d%w+)" ,
50+ -- tostring(val) is needed because __tostring may return a non-string value
51+ function (s ) if not syms [s ] then symn = symn + 1 ; syms [s ] = symn end return tostring (syms [s ]) end )) end
52+ local function safestr (s ) return type (s ) == " number" and (huge and snum [tostring (s )] or numformat :format (s ))
53+ or type (s ) ~= " string" and tostring (s ) -- escape NEWLINE/010 and EOF/026
54+ or (" %q" ):format (s ):gsub (" \010 " ," n" ):gsub (" \026 " ," \\ 026" ) end
55+ -- handle radix changes in some locales
56+ if opts .fixradix and (" .1f" ):format (1.2 ) ~= " 1.2" then
57+ local origsafestr = safestr
58+ safestr = function (s ) return type (s ) == " number"
59+ and (nohuge and snum [tostring (s )] or numformat :format (s ):gsub (" ," ," ." )) or origsafestr (s )
60+ end
61+ end
62+ local function comment (s ,l ) return comm and (l or 0 ) < comm and ' --[[' .. select (2 , pcall (tostring , s )).. ' ]]' or ' ' end
63+ local function globerr (s ,l ) return globals [s ] and globals [s ].. comment (s ,l ) or not fatal
64+ and safestr (select (2 , pcall (tostring , s ))) or error (" Can't serialize " .. tostring (s )) end
65+ local function safename (path , name ) -- generates foo.bar, foo[3], or foo['b a r']
66+ local n = name == nil and ' ' or name
67+ local plain = type (n ) == " string" and n :match (" ^[%l%u_][%w_]*$" ) and not keyword [n ]
68+ local safe = plain and n or ' [' .. safestr (n ).. ' ]'
69+ return (path or ' ' ).. (plain and path and ' .' or ' ' ).. safe , safe end
70+ local alphanumsort = type (opts .sortkeys ) == ' function' and opts .sortkeys or function (k , o , n ) -- k=keys, o=originaltable, n=padding
71+ local maxn , to = tonumber (n ) or 12 , {number = ' a' , string = ' b' }
72+ local function padnum (d ) return (" %0" .. tostring (maxn ).. " d" ):format (tonumber (d )) end
73+ table.sort (k , function (a ,b )
74+ -- sort numeric keys first: k[key] is not nil for numerical keys
75+ return (k [a ] ~= nil and 0 or to [type (a )] or ' z' ).. (tostring (a ):gsub (" %d+" ,padnum ))
76+ < (k [b ] ~= nil and 0 or to [type (b )] or ' z' ).. (tostring (b ):gsub (" %d+" ,padnum )) end ) end
77+ local function val2str (t , name , indent , insref , path , plainindex , level )
78+ local ttype , level , mt = type (t ), (level or 0 ), getmetatable (t )
79+ local spath , sname = safename (path , name )
80+ local tag = plainindex and
81+ ((type (name ) == " number" ) and ' ' or name .. space .. ' =' .. space ) or
82+ (name ~= nil and sname .. space .. ' =' .. space or ' ' )
83+ if seen [t ] then -- already seen this element
84+ sref [# sref + 1 ] = spath .. space .. ' =' .. space .. seen [t ]
85+ return tag .. ' nil' .. comment (' ref' , level )
86+ end
87+ -- protect from those cases where __tostring may fail
88+ if type (mt ) == ' table' and metatostring ~= false then
89+ local to , tr = pcall (function () return mt .__tostring (t ) end )
90+ local so , sr = pcall (function () return mt .__serialize (t ) end )
91+ if (to or so ) then -- knows how to serialize itself
92+ seen [t ] = insref or spath
93+ t = so and sr or tr
94+ ttype = type (t )
95+ end -- new value falls through to be serialized
96+ end
97+ if ttype == " table" then
98+ if level >= maxl then return tag .. ' {}' .. comment (' maxlvl' , level ) end
99+ seen [t ] = insref or spath
100+ if next (t ) == nil then return tag .. ' {}' .. comment (t , level ) end -- table empty
101+ if maxlen and maxlen < 0 then return tag .. ' {}' .. comment (' maxlen' , level ) end
102+ local maxn , o , out = math.min (# t , maxnum or # t ), {}, {}
103+ for key = 1 , maxn do o [key ] = key end
104+ if not maxnum or # o < maxnum then
105+ local n = # o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables
106+ for key in pairs (t ) do
107+ if o [key ] ~= key then n = n + 1 ; o [n ] = key end
108+ end
109+ end
110+ if maxnum and # o > maxnum then o [maxnum + 1 ] = nil end
111+ if opts .sortkeys and # o > maxn then alphanumsort (o , t , opts .sortkeys ) end
112+ local sparse = sparse and # o > maxn -- disable sparsness if only numeric keys (shorter output)
113+ for n , key in ipairs (o ) do
114+ local value , ktype , plainindex = t [key ], type (key ), n <= maxn and not sparse
115+ if opts .valignore and opts .valignore [value ] -- skip ignored values; do nothing
116+ or opts .keyallow and not opts .keyallow [key ]
117+ or opts .keyignore and opts .keyignore [key ]
118+ or opts .valtypeignore and opts .valtypeignore [type (value )] -- skipping ignored value types
119+ or sparse and value == nil then -- skipping nils; do nothing
120+ elseif ktype == ' table' or ktype == ' function' or badtype [ktype ] then
121+ if not seen [key ] and not globals [key ] then
122+ sref [# sref + 1 ] = ' placeholder'
123+ local sname = safename (iname , gensym (key )) -- iname is table for local variables
124+ sref [# sref ] = val2str (key ,sname ,indent ,sname ,iname ,true )
125+ end
126+ sref [# sref + 1 ] = ' placeholder'
127+ local path = seen [t ].. ' [' .. tostring (seen [key ] or globals [key ] or gensym (key )).. ' ]'
128+ sref [# sref ] = path .. space .. ' =' .. space .. tostring (seen [value ] or val2str (value ,nil ,indent ,path ))
129+ else
130+ out [# out + 1 ] = val2str (value ,key ,indent ,nil ,seen [t ],plainindex ,level + 1 )
131+ if maxlen then
132+ maxlen = maxlen - # out [# out ]
133+ if maxlen < 0 then break end
134+ end
135+ end
136+ end
137+ local prefix = string.rep (indent or ' ' , level )
138+ local head = indent and ' {\n ' .. prefix .. indent or ' {'
139+ local body = table.concat (out , ' ,' .. (indent and ' \n ' .. prefix .. indent or space ))
140+ local tail = indent and " \n " .. prefix .. ' }' or ' }'
141+ return (custom and custom (tag ,head ,body ,tail ,level ) or tag .. head .. body .. tail ).. comment (t , level )
142+ elseif badtype [ttype ] then
143+ seen [t ] = insref or spath
144+ return tag .. globerr (t , level )
145+ elseif ttype == ' function' then
146+ seen [t ] = insref or spath
147+ if opts .nocode then return tag .. " function() --[[..skipped..]] end" .. comment (t , level ) end
148+ local ok , res = pcall (string.dump , t , true )
149+ local func = ok and " ((loadstring or load)(" .. safestr (res ).. " ,'@serialized'))" .. comment (t , level )
150+ return tag .. (func or globerr (t , level ))
151+ else return tag .. safestr (t ) end -- handle all other types
152+ end
153+ local sepr = indent and " \n " or " ;" .. space
154+ local body = val2str (t , name , indent ) -- this call also populates sref
155+ local tail = # sref > 1 and table.concat (sref , sepr ).. sepr or ' '
156+ local warn = opts .comment and # sref > 1 and space .. " --[[incomplete output with shared/self-references skipped]]" or ' '
157+ return not name and body .. warn or " do local " .. body .. sepr .. tail .. " return " .. name .. sepr .. " end"
158+ end
159+
160+ local function deserialize (data , opts )
161+ local env = (opts and opts .safe == false ) and G
162+ or setmetatable ({}, {
163+ __index = function (t ,k ) return t end ,
164+ __call = function (t ,...) error (" cannot call functions" ) end
165+ })
166+ local f , res = (loadstring or load )(' return ' .. data , nil , nil , env )
167+ if not f then f , res = (loadstring or load )(data , nil , nil , env ) end
168+ if not f then return f , res end
169+ if setfenv then setfenv (f , env ) end
170+ return pcall (f )
171+ end
172+
173+ local function merge (a , b ) if b then for k ,v in pairs (b ) do a [k ] = v end end ; return a ; end
174+
175+ serpent = {
176+ serialize = s ,
177+ load = deserialize ,
178+ dump = function (a , opts ) return s (a , merge ({name = ' _' , compact = true , sparse = true }, opts )) end ,
179+ line = function (a , opts ) return s (a , merge ({sortkeys = true , comment = true }, opts )) end ,
180+ block = function (a , opts ) return s (a , merge ({indent = ' ' , sortkeys = true , comment = true }, opts )) end
181+ }
0 commit comments