1 | local n, v = "serpent", 0.27 -- (C) 2012-13 Paul Kulchenko; MIT License |
2 | local c, d = "Paul Kulchenko", "Lua serializer and pretty printer" |
3 | local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'} |
4 | local badtype = {thread = true, userdata = true, cdata = true} |
5 | local keyword, globals, G = {}, {}, (_G or _ENV) |
6 | for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false', |
7 | 'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', |
8 | 'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end |
9 | for k,v in pairs(G) do globals[v] = k end -- build func to name mapping |
10 | for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do |
11 | if G[g] then for k,v in pairs(G[g]) do globals[v] = g..'.'..k end end end -- XX fixed by Stefan for TinyBrain |
12 | |
13 | local function s(t, opts) |
14 | local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum |
15 | local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge |
16 | local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge) |
17 | local iname, comm = '_'..(name or ''), opts.comment and (tonumber(opts.comment) or math.huge) |
18 | local seen, sref, syms, symn = {}, {'local '..iname..'={}'}, {}, 0 |
19 | local function gensym(val) return '_'..(tostring(tostring(val)):gsub("[^%w]",""):gsub("(%d%w+)", |
20 | -- tostring(val) is needed because __tostring may return a non-string value |
21 | function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return syms[s] end)) end |
22 | local function safestr(s) return type(s) == "number" and (huge and snum[tostring(s)] or s) |
23 | or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026 |
24 | or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end |
25 | local function comment(s,l) return comm and (l or 0) < comm and ' --[['..tostring(s)..']]' or '' end |
26 | local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal |
27 | and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end |
28 | local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r'] |
29 | local n = name == nil and '' or name |
30 | local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n] |
31 | local safe = plain and n or '['..safestr(n)..']' |
32 | return (path or '')..(plain and path and '.' or '')..safe, safe end |
33 | local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(k, o, n) -- k=keys, o=originaltable, n=padding |
34 | local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'} |
35 | local function padnum(d) return ("%0"..maxn.."d"):format(d) end |
36 | table.sort(k, function(a,b) |
37 | -- sort numeric keys first: k[key] is not nil for numerical keys |
38 | return (k[a] ~= nil and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum)) |
39 | < (k[b] ~= nil and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end |
40 | local function val2str(t, name, indent, insref, path, plainindex, level) |
41 | local ttype, level, mt = type(t), (level or 0), getmetatable(t) |
42 | local spath, sname = safename(path, name) |
43 | local tag = plainindex and |
44 | ((type(name) == "number") and '' or name..space..'='..space) or |
45 | (name ~= nil and sname..space..'='..space or '') |
46 | if seen[t] then -- already seen this element |
47 | sref[#sref+1] = spath..space..'='..space..seen[t] |
48 | return tag..'nil'..comment('ref', level) end |
49 | if type(mt) == 'table' and (mt.__serialize or mt.__tostring) then -- knows how to serialize itself |
50 | seen[t] = insref or spath |
51 | if mt.__serialize then t = mt.__serialize(t) else t = tostring(t) end |
52 | ttype = type(t) end -- new value falls through to be serialized |
53 | if ttype == "table" then |
54 | if level >= maxl then return tag..'{}'..comment('max', level) end |
55 | seen[t] = insref or spath |
56 | if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty |
57 | local maxn, o, out = math.min(#t, maxnum or #t), {}, {} |
58 | for key = 1, maxn do o[key] = key end |
59 | if not maxnum or #o < maxnum then |
60 | local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables |
61 | for key in pairs(t) do if o[key] ~= key then n = n + 1; o[n] = key end end end |
62 | if maxnum and #o > maxnum then o[maxnum+1] = nil end |
63 | if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end |
64 | local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output) |
65 | for n, key in ipairs(o) do |
66 | local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse |
67 | if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing |
68 | or opts.keyallow and not opts.keyallow[key] |
69 | or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types |
70 | or sparse and value == nil then -- skipping nils; do nothing |
71 | elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then |
72 | if not seen[key] and not globals[key] then |
73 | sref[#sref+1] = 'placeholder' |
74 | local sname = safename(iname, gensym(key)) -- iname is table for local variables |
75 | sref[#sref] = val2str(key,sname,indent,sname,iname,true) end |
76 | sref[#sref+1] = 'placeholder' |
77 | local path = seen[t]..'['..(seen[key] or globals[key] or gensym(key))..']' |
78 | sref[#sref] = path..space..'='..space..(seen[value] or val2str(value,nil,indent,path)) |
79 | else |
80 | out[#out+1] = val2str(value,key,indent,insref,seen[t],plainindex,level+1) |
81 | end |
82 | end |
83 | local prefix = string.rep(indent or '', level) |
84 | local head = indent and '{\n'..prefix..indent or '{' |
85 | local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space)) |
86 | local tail = indent and "\n"..prefix..'}' or '}' |
87 | return (custom and custom(tag,head,body,tail) or tag..head..body..tail)..comment(t, level) |
88 | elseif badtype[ttype] then |
89 | seen[t] = insref or spath |
90 | return tag..globerr(t, level) |
91 | elseif ttype == 'function' then |
92 | seen[t] = insref or spath |
93 | local ok, res = pcall(string.dump, t) |
94 | local func = ok and ((opts.nocode and "function() --[[..skipped..]] end" or |
95 | "loadstring("..safestr(res)..",'@serialized')")..comment(t, level)) |
96 | return tag..(func or globerr(t, level)) |
97 | else return tag..safestr(t) end -- handle all other types |
98 | end |
99 | local sepr = indent and "\n" or ";"..space |
100 | local body = val2str(t, name, indent) -- this call also populates sref |
101 | local tail = #sref>1 and table.concat(sref, sepr)..sepr or '' |
102 | local warn = opts.comment and #sref>1 and space.."--[[incomplete output with shared/self-references skipped]]" or '' |
103 | return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end" |
104 | end |
105 | |
106 | local function deserialize(data, opts) |
107 | local f, res = (loadstring or load)('return '..data) |
108 | if not f then f, res = (loadstring or load)(data) end |
109 | if not f then return f, res end |
110 | if opts and opts.safe == false then return pcall(f) end |
111 | |
112 | local count, thread = 0, coroutine.running() |
113 | local h, m, c = debug.gethook(thread) |
114 | debug.sethook(function (e, l) count = count + 1 |
115 | if count >= 3 then error("cannot call functions") end |
116 | end, "c") |
117 | local res = {pcall(f)} |
118 | count = 0 -- set again, otherwise it's tripped on the next sethook |
119 | debug.sethook(thread, h, m, c) |
120 | return (table.unpack or unpack)(res) |
121 | end |
122 | |
123 | local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end |
124 | return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s, |
125 | load = deserialize, |
126 | dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end, |
127 | line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end, |
128 | block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end } |
from https://github.com/pkulchenko/serpent/blob/master/src/serpent.lua
test run test run with input download show line numbers
Travelled to 12 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Recognizer | Recognition Result | Visualize | Recalc |
---|---|---|---|
#308 | 8133 | [visualize] |
Snippet ID: | #158 |
Snippet name: | serpent.lua |
Eternal ID of this version: | #158/1 |
Text MD5: | f8e324fdd991017a2a6cc1dd08fb52a4 |
Author: | stefan |
Category: | table serializers |
Type: | Lua code |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2014-01-13 03:57:34 |
Source code size: | 8133 bytes / 128 lines |
Pitched / IR pitched: | Yes / Yes |
Views / Downloads: | 1160 / 4037 |
Referenced in: | [show references] |