1
0
Fork 0
mirror of https://github.com/Reuh/candran.git synced 2025-10-27 17:59:30 +00:00

Add constexpr and __STR__ default macros

This commit is contained in:
Étienne Fildadut 2021-06-11 14:17:55 +02:00
parent 496a4ddafd
commit ea54376aa6
3 changed files with 736 additions and 287 deletions

View file

@ -1,5 +1,6 @@
#import("candran.util") #import("candran.util")
#import("candran.cmdline") #import("candran.cmdline")
#import("candran.serpent")
#import("compiler.lua54") #import("compiler.lua54")
#import("compiler.lua53") #import("compiler.lua53")
@ -176,6 +177,14 @@ function candran.preprocess(input, options={})
end end
end end
-- default macros
-- TODO make it optional
env.define("__STR__(x)", function(x) return ("%q"):format(x) end)
local s = require("candran.serpent")
env.define("constexpr(expr)", function(expr)
return s.block(assert(candran.load(expr))(), {fatal = true})
end)
-- compile & load preprocessor -- compile & load preprocessor
local preprocess, err = candran.compile(preprocessor, options) local preprocess, err = candran.compile(preprocessor, options)
if not preprocess then if not preprocess then

View file

@ -141,7 +141,277 @@ end -- ./candran/cmdline.lua:125
end -- ./candran/cmdline.lua:125 end -- ./candran/cmdline.lua:125
local cmdline = _() or cmdline -- ./candran/cmdline.lua:130 local cmdline = _() or cmdline -- ./candran/cmdline.lua:130
package["loaded"]["candran.cmdline"] = cmdline or true -- ./candran/cmdline.lua:131 package["loaded"]["candran.cmdline"] = cmdline or true -- ./candran/cmdline.lua:131
local function _() -- ./candran/cmdline.lua:135 local function _() -- ./candran/cmdline.lua:134
local n, v = "serpent", "0.302" -- ./candran/serpent.lua:24
local c, d = "Paul Kulchenko", "Lua serializer and pretty printer" -- ./candran/serpent.lua:25
local snum = { -- ./candran/serpent.lua:26
[tostring(1 / 0)] = "1/0 --[[math.huge]]", -- ./candran/serpent.lua:26
[tostring(- 1 / 0)] = "-1/0 --[[-math.huge]]", -- ./candran/serpent.lua:26
[tostring(0 / 0)] = "0/0" -- ./candran/serpent.lua:26
} -- ./candran/serpent.lua:26
local badtype = { -- ./candran/serpent.lua:27
["thread"] = true, -- ./candran/serpent.lua:27
["userdata"] = true, -- ./candran/serpent.lua:27
["cdata"] = true -- ./candran/serpent.lua:27
} -- ./candran/serpent.lua:27
local getmetatable = debug and debug["getmetatable"] or getmetatable -- ./candran/serpent.lua:28
local pairs = function(t) -- ./candran/serpent.lua:29
return next, t -- ./candran/serpent.lua:29
end -- ./candran/serpent.lua:29
local keyword, globals, G = {}, {}, (_G or _ENV) -- ./candran/serpent.lua:30
for _, k in ipairs({ -- ./candran/serpent.lua:31
"and", -- ./candran/serpent.lua:31
"break", -- ./candran/serpent.lua:31
"do", -- ./candran/serpent.lua:31
"else", -- ./candran/serpent.lua:31
"elseif", -- ./candran/serpent.lua:31
"end", -- ./candran/serpent.lua:31
"false", -- ./candran/serpent.lua:31
"for", -- ./candran/serpent.lua:32
"function", -- ./candran/serpent.lua:32
"goto", -- ./candran/serpent.lua:32
"if", -- ./candran/serpent.lua:32
"in", -- ./candran/serpent.lua:32
"local", -- ./candran/serpent.lua:32
"nil", -- ./candran/serpent.lua:32
"not", -- ./candran/serpent.lua:32
"or", -- ./candran/serpent.lua:32
"repeat", -- ./candran/serpent.lua:32
"return", -- ./candran/serpent.lua:33
"then", -- ./candran/serpent.lua:33
"true", -- ./candran/serpent.lua:33
"until", -- ./candran/serpent.lua:33
"while" -- ./candran/serpent.lua:33
}) do -- ./candran/serpent.lua:33
keyword[k] = true -- ./candran/serpent.lua:33
end -- ./candran/serpent.lua:33
for k, v in pairs(G) do -- ./candran/serpent.lua:34
globals[v] = k -- ./candran/serpent.lua:34
end -- ./candran/serpent.lua:34
for _, g in ipairs({ -- ./candran/serpent.lua:35
"coroutine", -- ./candran/serpent.lua:35
"debug", -- ./candran/serpent.lua:35
"io", -- ./candran/serpent.lua:35
"math", -- ./candran/serpent.lua:35
"string", -- ./candran/serpent.lua:35
"table", -- ./candran/serpent.lua:35
"os" -- ./candran/serpent.lua:35
}) do -- ./candran/serpent.lua:35
for k, v in pairs(type(G[g]) == "table" and G[g] or {}) do -- ./candran/serpent.lua:36
globals[v] = g .. "." .. k -- ./candran/serpent.lua:36
end -- ./candran/serpent.lua:36
end -- ./candran/serpent.lua:36
local function s(t, opts) -- ./candran/serpent.lua:38
local name, indent, fatal, maxnum = opts["name"], opts["indent"], opts["fatal"], opts["maxnum"] -- ./candran/serpent.lua:39
local sparse, custom, huge = opts["sparse"], opts["custom"], not opts["nohuge"] -- ./candran/serpent.lua:40
local space, maxl = (opts["compact"] and "" or " "), (opts["maxlevel"] or math["huge"]) -- ./candran/serpent.lua:41
local maxlen, metatostring = tonumber(opts["maxlength"]), opts["metatostring"] -- ./candran/serpent.lua:42
local iname, comm = "_" .. (name or ""), opts["comment"] and (tonumber(opts["comment"]) or math["huge"]) -- ./candran/serpent.lua:43
local numformat = opts["numformat"] or "%.17g" -- ./candran/serpent.lua:44
local seen, sref, syms, symn = {}, { "local " .. iname .. "={}" }, {}, 0 -- ./candran/serpent.lua:45
local function gensym(val) -- ./candran/serpent.lua:46
return "_" .. (tostring(tostring(val)):gsub("[^%w]", ""):gsub("(%d%w+)", function(s) -- ./candran/serpent.lua:48
if not syms[s] then -- ./candran/serpent.lua:48
symn = symn + 1 -- ./candran/serpent.lua:48
syms[s] = symn -- ./candran/serpent.lua:48
end -- ./candran/serpent.lua:48
return tostring(syms[s]) -- ./candran/serpent.lua:48
end)) -- ./candran/serpent.lua:48
end -- ./candran/serpent.lua:48
local function safestr(s) -- ./candran/serpent.lua:49
return type(s) == "number" and tostring(huge and snum[tostring(s)] or numformat:format(s)) or type(s) ~= "string" and tostring(s) or ("%q"):format(s):gsub("\
", "n"):gsub("\26", "\\026") -- ./candran/serpent.lua:51
end -- ./candran/serpent.lua:51
local function comment(s, l) -- ./candran/serpent.lua:52
return comm and (l or 0) < comm and " --[[" .. select(2, pcall(tostring, s)) .. "]]" or "" -- ./candran/serpent.lua:52
end -- ./candran/serpent.lua:52
local function globerr(s, l) -- ./candran/serpent.lua:53
return globals[s] and globals[s] .. comment(s, l) or not fatal and safestr(select(2, pcall(tostring, s))) or error("Can't serialize " .. tostring(s)) -- ./candran/serpent.lua:54
end -- ./candran/serpent.lua:54
local function safename(path, name) -- ./candran/serpent.lua:55
local n = name == nil and "" or name -- ./candran/serpent.lua:56
local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n] -- ./candran/serpent.lua:57
local safe = plain and n or "[" .. safestr(n) .. "]" -- ./candran/serpent.lua:58
return (path or "") .. (plain and path and "." or "") .. safe, safe -- ./candran/serpent.lua:59
end -- ./candran/serpent.lua:59
local alphanumsort = type(opts["sortkeys"]) == "function" and opts["sortkeys"] or function(k, o, n) -- ./candran/serpent.lua:60
local maxn, to = tonumber(n) or 12, { -- ./candran/serpent.lua:61
["number"] = "a", -- ./candran/serpent.lua:61
["string"] = "b" -- ./candran/serpent.lua:61
} -- ./candran/serpent.lua:61
local function padnum(d) -- ./candran/serpent.lua:62
return ("%0" .. tostring(maxn) .. "d"):format(tonumber(d)) -- ./candran/serpent.lua:62
end -- ./candran/serpent.lua:62
table["sort"](k, function(a, b) -- ./candran/serpent.lua:63
return (k[a] ~= nil and 0 or to[type(a)] or "z") .. (tostring(a):gsub("%d+", padnum)) < (k[b] ~= nil and 0 or to[type(b)] or "z") .. (tostring(b):gsub("%d+", padnum)) -- ./candran/serpent.lua:66
end) -- ./candran/serpent.lua:66
end -- ./candran/serpent.lua:66
local function val2str(t, name, indent, insref, path, plainindex, level) -- ./candran/serpent.lua:67
local ttype, level, mt = type(t), (level or 0), getmetatable(t) -- ./candran/serpent.lua:68
local spath, sname = safename(path, name) -- ./candran/serpent.lua:69
local tag = plainindex and ((type(name) == "number") and "" or name .. space .. "=" .. space) or (name ~= nil and sname .. space .. "=" .. space or "") -- ./candran/serpent.lua:72
if seen[t] then -- ./candran/serpent.lua:73
sref[# sref + 1] = spath .. space .. "=" .. space .. seen[t] -- ./candran/serpent.lua:74
return tag .. "nil" .. comment("ref", level) -- ./candran/serpent.lua:75
end -- ./candran/serpent.lua:75
if type(mt) == "table" and metatostring ~= false then -- ./candran/serpent.lua:77
local to, tr = pcall(function() -- ./candran/serpent.lua:78
return mt["__tostring"](t) -- ./candran/serpent.lua:78
end) -- ./candran/serpent.lua:78
local so, sr = pcall(function() -- ./candran/serpent.lua:79
return mt["__serialize"](t) -- ./candran/serpent.lua:79
end) -- ./candran/serpent.lua:79
if (to or so) then -- ./candran/serpent.lua:80
seen[t] = insref or spath -- ./candran/serpent.lua:81
t = so and sr or tr -- ./candran/serpent.lua:82
ttype = type(t) -- ./candran/serpent.lua:83
end -- ./candran/serpent.lua:83
end -- ./candran/serpent.lua:83
if ttype == "table" then -- ./candran/serpent.lua:86
if level >= maxl then -- ./candran/serpent.lua:87
return tag .. "{}" .. comment("maxlvl", level) -- ./candran/serpent.lua:87
end -- ./candran/serpent.lua:87
seen[t] = insref or spath -- ./candran/serpent.lua:88
if next(t) == nil then -- ./candran/serpent.lua:89
return tag .. "{}" .. comment(t, level) -- ./candran/serpent.lua:89
end -- ./candran/serpent.lua:89
if maxlen and maxlen < 0 then -- ./candran/serpent.lua:90
return tag .. "{}" .. comment("maxlen", level) -- ./candran/serpent.lua:90
end -- ./candran/serpent.lua:90
local maxn, o, out = math["min"](# t, maxnum or # t), {}, {} -- ./candran/serpent.lua:91
for key = 1, maxn do -- ./candran/serpent.lua:92
o[key] = key -- ./candran/serpent.lua:92
end -- ./candran/serpent.lua:92
if not maxnum or # o < maxnum then -- ./candran/serpent.lua:93
local n = # o -- ./candran/serpent.lua:94
for key in pairs(t) do -- ./candran/serpent.lua:95
if o[key] ~= key then -- ./candran/serpent.lua:95
n = n + 1 -- ./candran/serpent.lua:95
o[n] = key -- ./candran/serpent.lua:95
end -- ./candran/serpent.lua:95
end -- ./candran/serpent.lua:95
end -- ./candran/serpent.lua:95
if maxnum and # o > maxnum then -- ./candran/serpent.lua:96
o[maxnum + 1] = nil -- ./candran/serpent.lua:96
end -- ./candran/serpent.lua:96
if opts["sortkeys"] and # o > maxn then -- ./candran/serpent.lua:97
alphanumsort(o, t, opts["sortkeys"]) -- ./candran/serpent.lua:97
end -- ./candran/serpent.lua:97
local sparse = sparse and # o > maxn -- ./candran/serpent.lua:98
for n, key in ipairs(o) do -- ./candran/serpent.lua:99
local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse -- ./candran/serpent.lua:100
if opts["valignore"] and opts["valignore"][value] or opts["keyallow"] and not opts["keyallow"][key] or opts["keyignore"] and opts["keyignore"][key] or opts["valtypeignore"] and opts["valtypeignore"][type(value)] or sparse and value == nil then -- ./candran/serpent.lua:105
-- ./candran/serpent.lua:106
elseif ktype == "table" or ktype == "function" or badtype[ktype] then -- ./candran/serpent.lua:106
if not seen[key] and not globals[key] then -- ./candran/serpent.lua:107
sref[# sref + 1] = "placeholder" -- ./candran/serpent.lua:108
local sname = safename(iname, gensym(key)) -- ./candran/serpent.lua:109
sref[# sref] = val2str(key, sname, indent, sname, iname, true) -- ./candran/serpent.lua:110
end -- ./candran/serpent.lua:110
sref[# sref + 1] = "placeholder" -- ./candran/serpent.lua:111
local path = seen[t] .. "[" .. tostring(seen[key] or globals[key] or gensym(key)) .. "]" -- ./candran/serpent.lua:112
sref[# sref] = path .. space .. "=" .. space .. tostring(seen[value] or val2str(value, nil, indent, path)) -- ./candran/serpent.lua:113
else -- ./candran/serpent.lua:113
out[# out + 1] = val2str(value, key, indent, nil, seen[t], plainindex, level + 1) -- ./candran/serpent.lua:115
if maxlen then -- ./candran/serpent.lua:116
maxlen = maxlen - # out[# out] -- ./candran/serpent.lua:117
if maxlen < 0 then -- ./candran/serpent.lua:118
break -- ./candran/serpent.lua:118
end -- ./candran/serpent.lua:118
end -- ./candran/serpent.lua:118
end -- ./candran/serpent.lua:118
end -- ./candran/serpent.lua:118
local prefix = string["rep"](indent or "", level) -- ./candran/serpent.lua:122
local head = indent and "{\
" .. prefix .. indent or "{" -- ./candran/serpent.lua:123
local body = table["concat"](out, "," .. (indent and "\
" .. prefix .. indent or space)) -- ./candran/serpent.lua:124
local tail = indent and "\
" .. prefix .. "}" or "}" -- ./candran/serpent.lua:125
return (custom and custom(tag, head, body, tail, level) or tag .. head .. body .. tail) .. comment(t, level) -- ./candran/serpent.lua:126
elseif badtype[ttype] then -- ./candran/serpent.lua:127
seen[t] = insref or spath -- ./candran/serpent.lua:128
return tag .. globerr(t, level) -- ./candran/serpent.lua:129
elseif ttype == "function" then -- ./candran/serpent.lua:130
seen[t] = insref or spath -- ./candran/serpent.lua:131
if opts["nocode"] then -- ./candran/serpent.lua:132
return tag .. "function() --[[..skipped..]] end" .. comment(t, level) -- ./candran/serpent.lua:132
end -- ./candran/serpent.lua:132
local ok, res = pcall(string["dump"], t) -- ./candran/serpent.lua:133
local func = ok and "((loadstring or load)(" .. safestr(res) .. ",'@serialized'))" .. comment(t, level) -- ./candran/serpent.lua:134
return tag .. (func or globerr(t, level)) -- ./candran/serpent.lua:135
else -- ./candran/serpent.lua:135
return tag .. safestr(t) -- ./candran/serpent.lua:136
end -- ./candran/serpent.lua:136
end -- ./candran/serpent.lua:136
local sepr = indent and "\
" or ";" .. space -- ./candran/serpent.lua:138
local body = val2str(t, name, indent) -- ./candran/serpent.lua:139
local tail = # sref > 1 and table["concat"](sref, sepr) .. sepr or "" -- ./candran/serpent.lua:140
local warn = opts["comment"] and # sref > 1 and space .. "--[[incomplete output with shared/self-references skipped]]" or "" -- ./candran/serpent.lua:141
return not name and body .. warn or "do local " .. body .. sepr .. tail .. "return " .. name .. sepr .. "end" -- ./candran/serpent.lua:142
end -- ./candran/serpent.lua:142
local function deserialize(data, opts) -- ./candran/serpent.lua:145
local env = (opts and opts["safe"] == false) and G or setmetatable({}, { -- ./candran/serpent.lua:147
["__index"] = function(t, k) -- ./candran/serpent.lua:148
return t -- ./candran/serpent.lua:148
end, -- ./candran/serpent.lua:148
["__call"] = function(t, ...) -- ./candran/serpent.lua:149
error("cannot call functions") -- ./candran/serpent.lua:149
end -- ./candran/serpent.lua:149
}) -- ./candran/serpent.lua:149
local f, res = (loadstring or load)("return " .. data, nil, nil, env) -- ./candran/serpent.lua:151
if not f then -- ./candran/serpent.lua:152
f, res = (loadstring or load)(data, nil, nil, env) -- ./candran/serpent.lua:152
end -- ./candran/serpent.lua:152
if not f then -- ./candran/serpent.lua:153
return f, res -- ./candran/serpent.lua:153
end -- ./candran/serpent.lua:153
if setfenv then -- ./candran/serpent.lua:154
setfenv(f, env) -- ./candran/serpent.lua:154
end -- ./candran/serpent.lua:154
return pcall(f) -- ./candran/serpent.lua:155
end -- ./candran/serpent.lua:155
local function merge(a, b) -- ./candran/serpent.lua:158
if b then -- ./candran/serpent.lua:158
for k, v in pairs(b) do -- ./candran/serpent.lua:158
a[k] = v -- ./candran/serpent.lua:158
end -- ./candran/serpent.lua:158
end -- ./candran/serpent.lua:158
return a -- ./candran/serpent.lua:158
end -- ./candran/serpent.lua:158
return { -- ./candran/serpent.lua:159
["_NAME"] = n, -- ./candran/serpent.lua:159
["_COPYRIGHT"] = c, -- ./candran/serpent.lua:159
["_DESCRIPTION"] = d, -- ./candran/serpent.lua:159
["_VERSION"] = v, -- ./candran/serpent.lua:159
["serialize"] = s, -- ./candran/serpent.lua:159
["load"] = deserialize, -- ./candran/serpent.lua:160
["dump"] = function(a, opts) -- ./candran/serpent.lua:161
return s(a, merge({ -- ./candran/serpent.lua:161
["name"] = "_", -- ./candran/serpent.lua:161
["compact"] = true, -- ./candran/serpent.lua:161
["sparse"] = true -- ./candran/serpent.lua:161
}, opts)) -- ./candran/serpent.lua:161
end, -- ./candran/serpent.lua:161
["line"] = function(a, opts) -- ./candran/serpent.lua:162
return s(a, merge({ -- ./candran/serpent.lua:162
["sortkeys"] = true, -- ./candran/serpent.lua:162
["comment"] = true -- ./candran/serpent.lua:162
}, opts)) -- ./candran/serpent.lua:162
end, -- ./candran/serpent.lua:162
["block"] = function(a, opts) -- ./candran/serpent.lua:163
return s(a, merge({ -- ./candran/serpent.lua:163
["indent"] = " ", -- ./candran/serpent.lua:163
["sortkeys"] = true, -- ./candran/serpent.lua:163
["comment"] = true -- ./candran/serpent.lua:163
}, opts)) -- ./candran/serpent.lua:163
end -- ./candran/serpent.lua:163
} -- ./candran/serpent.lua:163
end -- ./candran/serpent.lua:163
local serpent = _() or serpent -- ./candran/serpent.lua:167
package["loaded"]["candran.serpent"] = serpent or true -- ./candran/serpent.lua:168
local function _() -- ./candran/serpent.lua:172
local util = require("candran.util") -- ./compiler/lua54.can:1 local util = require("candran.util") -- ./compiler/lua54.can:1
local targetName = "Lua 5.4" -- ./compiler/lua54.can:3 local targetName = "Lua 5.4" -- ./compiler/lua54.can:3
local unpack = unpack or table["unpack"] -- ./compiler/lua54.can:5 local unpack = unpack or table["unpack"] -- ./compiler/lua54.can:5
@ -6797,311 +7067,318 @@ return parser -- ./candran/can-parser/parser.lua:807
end -- ./candran/can-parser/parser.lua:807 end -- ./candran/can-parser/parser.lua:807
local parser = _() or parser -- ./candran/can-parser/parser.lua:811 local parser = _() or parser -- ./candran/can-parser/parser.lua:811
package["loaded"]["candran.can-parser.parser"] = parser or true -- ./candran/can-parser/parser.lua:812 package["loaded"]["candran.can-parser.parser"] = parser or true -- ./candran/can-parser/parser.lua:812
local unpack = unpack or table["unpack"] -- candran.can:15 local unpack = unpack or table["unpack"] -- candran.can:16
local candran = { ["VERSION"] = "0.14.0" } -- candran.can:18 local candran = { ["VERSION"] = "0.14.0" } -- candran.can:19
package["loaded"]["candran"] = candran -- candran.can:20 package["loaded"]["candran"] = candran -- candran.can:21
candran["default"] = { -- candran.can:23 candran["default"] = { -- candran.can:24
["target"] = "lua54", -- candran.can:24 ["target"] = "lua54", -- candran.can:25
["indentation"] = "", -- candran.can:25 ["indentation"] = "", -- candran.can:26
["newline"] = "\ ["newline"] = "\
", -- candran.can:26 ", -- candran.can:27
["variablePrefix"] = "__CAN_", -- candran.can:27 ["variablePrefix"] = "__CAN_", -- candran.can:28
["mapLines"] = true, -- candran.can:28 ["mapLines"] = true, -- candran.can:29
["chunkname"] = "nil", -- candran.can:29 ["chunkname"] = "nil", -- candran.can:30
["rewriteErrors"] = true -- candran.can:30 ["rewriteErrors"] = true -- candran.can:31
} -- candran.can:30 } -- candran.can:31
if _VERSION == "Lua 5.1" then -- candran.can:34 if _VERSION == "Lua 5.1" then -- candran.can:35
if package["loaded"]["jit"] then -- candran.can:35 if package["loaded"]["jit"] then -- candran.can:36
candran["default"]["target"] = "luajit" -- candran.can:36 candran["default"]["target"] = "luajit" -- candran.can:37
else -- candran.can:36 else -- candran.can:37
candran["default"]["target"] = "lua51" -- candran.can:38 candran["default"]["target"] = "lua51" -- candran.can:39
end -- candran.can:38 end -- candran.can:39
elseif _VERSION == "Lua 5.2" then -- candran.can:40 elseif _VERSION == "Lua 5.2" then -- candran.can:41
candran["default"]["target"] = "lua52" -- candran.can:41 candran["default"]["target"] = "lua52" -- candran.can:42
elseif _VERSION == "Lua 5.3" then -- candran.can:42 elseif _VERSION == "Lua 5.3" then -- candran.can:43
candran["default"]["target"] = "lua53" -- candran.can:43 candran["default"]["target"] = "lua53" -- candran.can:44
end -- candran.can:43 end -- candran.can:44
candran["preprocess"] = function(input, options) -- candran.can:53 candran["preprocess"] = function(input, options) -- candran.can:54
if options == nil then options = {} end -- candran.can:53 if options == nil then options = {} end -- candran.can:54
options = util["merge"](candran["default"], options) -- candran.can:54 options = util["merge"](candran["default"], options) -- candran.can:55
local macros = { -- candran.can:55 local macros = { -- candran.can:56
["functions"] = {}, -- candran.can:56 ["functions"] = {}, -- candran.can:57
["variables"] = {} -- candran.can:57 ["variables"] = {} -- candran.can:58
} -- candran.can:57 } -- candran.can:58
local preprocessor = "" -- candran.can:61 local preprocessor = "" -- candran.can:62
local i = 0 -- candran.can:62 local i = 0 -- candran.can:63
local inLongString = false -- candran.can:63 local inLongString = false -- candran.can:64
local inComment = false -- candran.can:64 local inComment = false -- candran.can:65
for line in (input .. "\ for line in (input .. "\
"):gmatch("(.-\ "):gmatch("(.-\
)") do -- candran.can:65 )") do -- candran.can:66
i = i + (1) -- candran.can:66 i = i + (1) -- candran.can:67
if inComment then -- candran.can:68 if inComment then -- candran.can:69
inComment = not line:match("%]%]") -- candran.can:69 inComment = not line:match("%]%]") -- candran.can:70
elseif inLongString then -- candran.can:70 elseif inLongString then -- candran.can:71
inLongString = not line:match("%]%]") -- candran.can:71 inLongString = not line:match("%]%]") -- candran.can:72
else -- candran.can:71 else -- candran.can:72
if line:match("[^%-]%[%[") then -- candran.can:73 if line:match("[^%-]%[%[") then -- candran.can:74
inLongString = true -- candran.can:74 inLongString = true -- candran.can:75
elseif line:match("%-%-%[%[") then -- candran.can:75 elseif line:match("%-%-%[%[") then -- candran.can:76
inComment = true -- candran.can:76 inComment = true -- candran.can:77
end -- candran.can:76 end -- candran.can:77
end -- candran.can:76 end -- candran.can:77
if not inComment and not inLongString and line:match("^%s*#") and not line:match("^#!") then -- candran.can:79 if not inComment and not inLongString and line:match("^%s*#") and not line:match("^#!") then -- candran.can:80
preprocessor = preprocessor .. (line:gsub("^%s*#", "")) -- candran.can:80 preprocessor = preprocessor .. (line:gsub("^%s*#", "")) -- candran.can:81
else -- candran.can:80 else -- candran.can:81
local l = line:sub(1, - 2) -- candran.can:82 local l = line:sub(1, - 2) -- candran.can:83
if not inLongString and options["mapLines"] and not l:match("%-%- (.-)%:(%d+)$") then -- candran.can:83 if not inLongString and options["mapLines"] and not l:match("%-%- (.-)%:(%d+)$") then -- candran.can:84
preprocessor = preprocessor .. (("write(%q)"):format(l .. " -- " .. options["chunkname"] .. ":" .. i) .. "\ preprocessor = preprocessor .. (("write(%q)"):format(l .. " -- " .. options["chunkname"] .. ":" .. i) .. "\
") -- candran.can:84 ") -- candran.can:85
else -- candran.can:84 else -- candran.can:85
preprocessor = preprocessor .. (("write(%q)"):format(line:sub(1, - 2)) .. "\ preprocessor = preprocessor .. (("write(%q)"):format(line:sub(1, - 2)) .. "\
") -- candran.can:86 ") -- candran.can:87
end -- candran.can:86 end -- candran.can:87
end -- candran.can:86 end -- candran.can:87
end -- candran.can:86 end -- candran.can:87
preprocessor = preprocessor .. ("return output") -- candran.can:90 preprocessor = preprocessor .. ("return output") -- candran.can:91
local env = util["merge"](_G, options) -- candran.can:93 local env = util["merge"](_G, options) -- candran.can:94
env["candran"] = candran -- candran.can:95 env["candran"] = candran -- candran.can:96
env["output"] = "" -- candran.can:97 env["output"] = "" -- candran.can:98
env["import"] = function(modpath, margs) -- candran.can:104 env["import"] = function(modpath, margs) -- candran.can:105
if margs == nil then margs = {} end -- candran.can:104 if margs == nil then margs = {} end -- candran.can:105
local filepath = assert(util["search"](modpath, { -- candran.can:105 local filepath = assert(util["search"](modpath, { -- candran.can:106
"can", -- candran.can:105 "can", -- candran.can:106
"lua" -- candran.can:105 "lua" -- candran.can:106
}), "No module named \"" .. modpath .. "\"") -- candran.can:105 }), "No module named \"" .. modpath .. "\"") -- candran.can:106
local f = io["open"](filepath) -- candran.can:108 local f = io["open"](filepath) -- candran.can:109
if not f then -- candran.can:109 if not f then -- candran.can:110
error("can't open the module file to import") -- candran.can:109 error("can't open the module file to import") -- candran.can:110
end -- candran.can:109 end -- candran.can:110
margs = util["merge"](options, { -- candran.can:111 margs = util["merge"](options, { -- candran.can:112
["chunkname"] = filepath, -- candran.can:111 ["chunkname"] = filepath, -- candran.can:112
["loadLocal"] = true, -- candran.can:111 ["loadLocal"] = true, -- candran.can:112
["loadPackage"] = true -- candran.can:111 ["loadPackage"] = true -- candran.can:112
}, margs) -- candran.can:111 }, margs) -- candran.can:112
local modcontent, modmacros = assert(candran["preprocess"](f:read("*a"), margs)) -- candran.can:112 local modcontent, modmacros = assert(candran["preprocess"](f:read("*a"), margs)) -- candran.can:113
macros = util["recmerge"](macros, modmacros) -- candran.can:113 macros = util["recmerge"](macros, modmacros) -- candran.can:114
f:close() -- candran.can:114 f:close() -- candran.can:115
local modname = modpath:match("[^%.]+$") -- candran.can:117 local modname = modpath:match("[^%.]+$") -- candran.can:118
env["write"]("-- MODULE " .. modpath .. " --\ env["write"]("-- MODULE " .. modpath .. " --\
" .. "local function _()\ " .. "local function _()\
" .. modcontent .. "\ " .. modcontent .. "\
" .. "end\ " .. "end\
" .. (margs["loadLocal"] and ("local %s = _() or %s\ " .. (margs["loadLocal"] and ("local %s = _() or %s\
"):format(modname, modname) or "") .. (margs["loadPackage"] and ("package.loaded[%q] = %s or true\ "):format(modname, modname) or "") .. (margs["loadPackage"] and ("package.loaded[%q] = %s or true\
"):format(modpath, margs["loadLocal"] and modname or "_()") or "") .. "-- END OF MODULE " .. modpath .. " --") -- candran.can:126 "):format(modpath, margs["loadLocal"] and modname or "_()") or "") .. "-- END OF MODULE " .. modpath .. " --") -- candran.can:127
end -- candran.can:126 end -- candran.can:127
env["include"] = function(file) -- candran.can:131 env["include"] = function(file) -- candran.can:132
local f = io["open"](file) -- candran.can:132 local f = io["open"](file) -- candran.can:133
if not f then -- candran.can:133 if not f then -- candran.can:134
error("can't open the file " .. file .. " to include") -- candran.can:133 error("can't open the file " .. file .. " to include") -- candran.can:134
end -- candran.can:133 end -- candran.can:134
env["write"](f:read("*a")) -- candran.can:134 env["write"](f:read("*a")) -- candran.can:135
f:close() -- candran.can:135 f:close() -- candran.can:136
end -- candran.can:135 end -- candran.can:136
env["write"] = function(...) -- candran.can:139 env["write"] = function(...) -- candran.can:140
env["output"] = env["output"] .. (table["concat"]({ ... }, "\9") .. "\ env["output"] = env["output"] .. (table["concat"]({ ... }, "\9") .. "\
") -- candran.can:140 ") -- candran.can:141
end -- candran.can:140 end -- candran.can:141
env["placeholder"] = function(name) -- candran.can:144 env["placeholder"] = function(name) -- candran.can:145
if env[name] then -- candran.can:145 if env[name] then -- candran.can:146
env["write"](env[name]) -- candran.can:146 env["write"](env[name]) -- candran.can:147
end -- candran.can:146 end -- candran.can:147
end -- candran.can:146 end -- candran.can:147
env["define"] = function(identifier, replacement) -- candran.can:149 env["define"] = function(identifier, replacement) -- candran.can:150
local iast, ierr = parser["parsemacroidentifier"](identifier, options["chunkname"]) -- candran.can:151 local iast, ierr = parser["parsemacroidentifier"](identifier, options["chunkname"]) -- candran.can:152
if not iast then -- candran.can:152 if not iast then -- candran.can:153
return error(("in macro identifier: %s"):format(tostring(ierr))) -- candran.can:153 return error(("in macro identifier: %s"):format(tostring(ierr))) -- candran.can:154
end -- candran.can:153 end -- candran.can:154
if type(replacement) == "string" then -- candran.can:156 if type(replacement) == "string" then -- candran.can:157
local rast, rerr = parser["parse"](replacement, options["chunkname"]) -- candran.can:157 local rast, rerr = parser["parse"](replacement, options["chunkname"]) -- candran.can:158
if not rast then -- candran.can:158 if not rast then -- candran.can:159
return error(("in macro replacement: %s"):format(tostring(rerr))) -- candran.can:159 return error(("in macro replacement: %s"):format(tostring(rerr))) -- candran.can:160
end -- candran.can:159 end -- candran.can:160
if # rast == 1 and rast[1]["tag"] == "Push" and rast[1]["implicit"] then -- candran.can:162 if # rast == 1 and rast[1]["tag"] == "Push" and rast[1]["implicit"] then -- candran.can:163
rast = rast[1][1] -- candran.can:163 rast = rast[1][1] -- candran.can:164
end -- candran.can:163 end -- candran.can:164
replacement = rast -- candran.can:165 replacement = rast -- candran.can:166
elseif type(replacement) ~= "function" then -- candran.can:166 elseif type(replacement) ~= "function" then -- candran.can:167
error("bad argument #2 to 'define' (string or function expected)") -- candran.can:167 error("bad argument #2 to 'define' (string or function expected)") -- candran.can:168
end -- candran.can:167 end -- candran.can:168
if iast["tag"] == "MacroFunction" then -- candran.can:170 if iast["tag"] == "MacroFunction" then -- candran.can:171
macros["functions"][iast[1][1]] = { -- candran.can:171 macros["functions"][iast[1][1]] = { -- candran.can:172
["args"] = iast[2], -- candran.can:171 ["args"] = iast[2], -- candran.can:172
["replacement"] = replacement -- candran.can:171 ["replacement"] = replacement -- candran.can:172
} -- candran.can:171 } -- candran.can:172
elseif iast["tag"] == "Id" then -- candran.can:172 elseif iast["tag"] == "Id" then -- candran.can:173
macros["variables"][iast[1]] = replacement -- candran.can:173 macros["variables"][iast[1]] = replacement -- candran.can:174
else -- candran.can:173 else -- candran.can:174
error(("invalid macro type %s"):format(tostring(iast["tag"]))) -- candran.can:175 error(("invalid macro type %s"):format(tostring(iast["tag"]))) -- candran.can:176
end -- candran.can:175 end -- candran.can:176
end -- candran.can:175 end -- candran.can:176
local preprocess, err = candran["compile"](preprocessor, options) -- candran.can:180 env["define"]("__STR__(x)", function(x) -- candran.can:182
if not preprocess then -- candran.can:181 return ("%q"):format(x) -- candran.can:182
return nil, "in preprocessor: " .. err -- candran.can:182 end) -- candran.can:182
end -- candran.can:182 local s = require("candran.serpent") -- candran.can:183
preprocess, err = util["load"](preprocessor, "candran preprocessor", env) -- candran.can:185 env["define"]("constexpr(expr)", function(expr) -- candran.can:184
if not preprocess then -- candran.can:186 return s["block"](assert(candran["load"](expr))(), { ["fatal"] = true }) -- candran.can:185
return nil, "in preprocessor: " .. err -- candran.can:187 end) -- candran.can:185
end -- candran.can:187 local preprocess, err = candran["compile"](preprocessor, options) -- candran.can:189
local success, output = pcall(preprocess) -- candran.can:191 if not preprocess then -- candran.can:190
if not success then -- candran.can:192 return nil, "in preprocessor: " .. err -- candran.can:191
return nil, "in preprocessor: " .. output -- candran.can:193 end -- candran.can:191
end -- candran.can:193 preprocess, err = util["load"](preprocessor, "candran preprocessor", env) -- candran.can:194
return output, macros -- candran.can:196 if not preprocess then -- candran.can:195
return nil, "in preprocessor: " .. err -- candran.can:196
end -- candran.can:196 end -- candran.can:196
candran["compile"] = function(input, options, macros) -- candran.can:206 local success, output = pcall(preprocess) -- candran.can:200
if options == nil then options = {} end -- candran.can:206 if not success then -- candran.can:201
options = util["merge"](candran["default"], options) -- candran.can:207 return nil, "in preprocessor: " .. output -- candran.can:202
local ast, errmsg = parser["parse"](input, options["chunkname"]) -- candran.can:209 end -- candran.can:202
if not ast then -- candran.can:211 return output, macros -- candran.can:205
return nil, errmsg -- candran.can:212 end -- candran.can:205
end -- candran.can:212 candran["compile"] = function(input, options, macros) -- candran.can:215
return require("compiler." .. options["target"])(input, ast, options, macros) -- candran.can:215 if options == nil then options = {} end -- candran.can:215
end -- candran.can:215 options = util["merge"](candran["default"], options) -- candran.can:216
candran["make"] = function(code, options) -- candran.can:224 local ast, errmsg = parser["parse"](input, options["chunkname"]) -- candran.can:218
local r, err = candran["preprocess"](code, options) -- candran.can:225 if not ast then -- candran.can:220
if r then -- candran.can:226 return nil, errmsg -- candran.can:221
r, err = candran["compile"](r, options, err) -- candran.can:227 end -- candran.can:221
if r then -- candran.can:228 return require("compiler." .. options["target"])(input, ast, options, macros) -- candran.can:224
return r -- candran.can:229 end -- candran.can:224
end -- candran.can:229 candran["make"] = function(code, options) -- candran.can:233
end -- candran.can:229 local r, err = candran["preprocess"](code, options) -- candran.can:234
return r, err -- candran.can:232 if r then -- candran.can:235
end -- candran.can:232 r, err = candran["compile"](r, options, err) -- candran.can:236
local errorRewritingActive = false -- candran.can:235 if r then -- candran.can:237
local codeCache = {} -- candran.can:236 return r -- candran.can:238
candran["loadfile"] = function(filepath, env, options) -- candran.can:239 end -- candran.can:238
local f, err = io["open"](filepath) -- candran.can:240 end -- candran.can:238
if not f then -- candran.can:241 return r, err -- candran.can:241
return nil, ("cannot open %s"):format(tostring(err)) -- candran.can:242 end -- candran.can:241
end -- candran.can:242 local errorRewritingActive = false -- candran.can:244
local content = f:read("*a") -- candran.can:244 local codeCache = {} -- candran.can:245
f:close() -- candran.can:245 candran["loadfile"] = function(filepath, env, options) -- candran.can:248
return candran["load"](content, filepath, env, options) -- candran.can:247 local f, err = io["open"](filepath) -- candran.can:249
end -- candran.can:247 if not f then -- candran.can:250
candran["load"] = function(chunk, chunkname, env, options) -- candran.can:252 return nil, ("cannot open %s"):format(tostring(err)) -- candran.can:251
if options == nil then options = {} end -- candran.can:252 end -- candran.can:251
options = util["merge"]({ ["chunkname"] = tostring(chunkname or chunk) }, options) -- candran.can:253 local content = f:read("*a") -- candran.can:253
local code, err = candran["make"](chunk, options) -- candran.can:255 f:close() -- candran.can:254
if not code then -- candran.can:256 return candran["load"](content, filepath, env, options) -- candran.can:256
return code, err -- candran.can:257 end -- candran.can:256
end -- candran.can:257 candran["load"] = function(chunk, chunkname, env, options) -- candran.can:261
codeCache[options["chunkname"]] = code -- candran.can:260 if options == nil then options = {} end -- candran.can:261
local f -- candran.can:261 options = util["merge"]({ ["chunkname"] = tostring(chunkname or chunk) }, options) -- candran.can:262
f, err = util["load"](code, ("=%s(%s)"):format(options["chunkname"], "compiled candran"), env) -- candran.can:262 local code, err = candran["make"](chunk, options) -- candran.can:264
if f == nil then -- candran.can:267 if not code then -- candran.can:265
return f, "candran unexpectedly generated invalid code: " .. err -- candran.can:268 return code, err -- candran.can:266
end -- candran.can:268 end -- candran.can:266
if options["rewriteErrors"] == false then -- candran.can:271 codeCache[options["chunkname"]] = code -- candran.can:269
return f -- candran.can:272 local f -- candran.can:270
else -- candran.can:272 f, err = util["load"](code, ("=%s(%s)"):format(options["chunkname"], "compiled candran"), env) -- candran.can:271
return function(...) -- candran.can:274 if f == nil then -- candran.can:276
if not errorRewritingActive then -- candran.can:275 return f, "candran unexpectedly generated invalid code: " .. err -- candran.can:277
errorRewritingActive = true -- candran.can:276 end -- candran.can:277
local t = { xpcall(f, candran["messageHandler"], ...) } -- candran.can:277 if options["rewriteErrors"] == false then -- candran.can:280
errorRewritingActive = false -- candran.can:278 return f -- candran.can:281
if t[1] == false then -- candran.can:279 else -- candran.can:281
error(t[2], 0) -- candran.can:280 return function(...) -- candran.can:283
end -- candran.can:280 if not errorRewritingActive then -- candran.can:284
return unpack(t, 2) -- candran.can:282 errorRewritingActive = true -- candran.can:285
else -- candran.can:282 local t = { xpcall(f, candran["messageHandler"], ...) } -- candran.can:286
return f(...) -- candran.can:284 errorRewritingActive = false -- candran.can:287
end -- candran.can:284 if t[1] == false then -- candran.can:288
end -- candran.can:284 error(t[2], 0) -- candran.can:289
end -- candran.can:284 end -- candran.can:289
end -- candran.can:284 return unpack(t, 2) -- candran.can:291
candran["dofile"] = function(filename, options) -- candran.can:292 else -- candran.can:291
local f, err = candran["loadfile"](filename, nil, options) -- candran.can:293 return f(...) -- candran.can:293
if f == nil then -- candran.can:295 end -- candran.can:293
error(err) -- candran.can:296 end -- candran.can:293
else -- candran.can:296 end -- candran.can:293
return f() -- candran.can:298 end -- candran.can:293
end -- candran.can:298 candran["dofile"] = function(filename, options) -- candran.can:301
end -- candran.can:298 local f, err = candran["loadfile"](filename, nil, options) -- candran.can:302
candran["messageHandler"] = function(message, noTraceback) -- candran.can:304 if f == nil then -- candran.can:304
error(err) -- candran.can:305
else -- candran.can:305
return f() -- candran.can:307
end -- candran.can:307
end -- candran.can:307
candran["messageHandler"] = function(message, noTraceback) -- candran.can:313
if not noTraceback and not message:match("\ if not noTraceback and not message:match("\
stack traceback:\ stack traceback:\
") then -- candran.can:305 ") then -- candran.can:314
message = debug["traceback"](message, 2) -- candran.can:306 message = debug["traceback"](message, 2) -- candran.can:315
end -- candran.can:306 end -- candran.can:315
return message:gsub("(\ return message:gsub("(\
?%s*)([^\ ?%s*)([^\
]-)%:(%d+)%:", function(indentation, source, line) -- candran.can:308 ]-)%:(%d+)%:", function(indentation, source, line) -- candran.can:317
line = tonumber(line) -- candran.can:309 line = tonumber(line) -- candran.can:318
local originalFile -- candran.can:311 local originalFile -- candran.can:320
local strName = source:match("^(.-)%(compiled candran%)$") -- candran.can:312 local strName = source:match("^(.-)%(compiled candran%)$") -- candran.can:321
if strName then -- candran.can:313 if strName then -- candran.can:322
if codeCache[strName] then -- candran.can:314 if codeCache[strName] then -- candran.can:323
originalFile = codeCache[strName] -- candran.can:315 originalFile = codeCache[strName] -- candran.can:324
source = strName -- candran.can:316 source = strName -- candran.can:325
end -- candran.can:316 end -- candran.can:325
else -- candran.can:316 else -- candran.can:325
do -- candran.can:319 do -- candran.can:328
local fi -- candran.can:319 local fi -- candran.can:328
fi = io["open"](source, "r") -- candran.can:319 fi = io["open"](source, "r") -- candran.can:328
if fi then -- candran.can:319 if fi then -- candran.can:328
originalFile = fi:read("*a") -- candran.can:320 originalFile = fi:read("*a") -- candran.can:329
fi:close() -- candran.can:321 fi:close() -- candran.can:330
end -- candran.can:321 end -- candran.can:330
end -- candran.can:321 end -- candran.can:330
end -- candran.can:321 end -- candran.can:330
if originalFile then -- candran.can:325 if originalFile then -- candran.can:334
local i = 0 -- candran.can:326 local i = 0 -- candran.can:335
for l in (originalFile .. "\ for l in (originalFile .. "\
"):gmatch("([^\ "):gmatch("([^\
]*)\ ]*)\
") do -- candran.can:327 ") do -- candran.can:336
i = i + 1 -- candran.can:328 i = i + 1 -- candran.can:337
if i == line then -- candran.can:329 if i == line then -- candran.can:338
local extSource, lineMap = l:match(".*%-%- (.-)%:(%d+)$") -- candran.can:330 local extSource, lineMap = l:match(".*%-%- (.-)%:(%d+)$") -- candran.can:339
if lineMap then -- candran.can:331 if lineMap then -- candran.can:340
if extSource ~= source then -- candran.can:332 if extSource ~= source then -- candran.can:341
return indentation .. extSource .. ":" .. lineMap .. "(" .. extSource .. ":" .. line .. "):" -- candran.can:333 return indentation .. extSource .. ":" .. lineMap .. "(" .. extSource .. ":" .. line .. "):" -- candran.can:342
else -- candran.can:333 else -- candran.can:342
return indentation .. extSource .. ":" .. lineMap .. "(" .. line .. "):" -- candran.can:335 return indentation .. extSource .. ":" .. lineMap .. "(" .. line .. "):" -- candran.can:344
end -- candran.can:335 end -- candran.can:344
end -- candran.can:335 end -- candran.can:344
break -- candran.can:338 break -- candran.can:347
end -- candran.can:338 end -- candran.can:347
end -- candran.can:338 end -- candran.can:347
end -- candran.can:338 end -- candran.can:347
end) -- candran.can:338 end) -- candran.can:347
end -- candran.can:338 end -- candran.can:347
candran["searcher"] = function(modpath) -- candran.can:346 candran["searcher"] = function(modpath) -- candran.can:355
local filepath = util["search"](modpath, { "can" }) -- candran.can:347 local filepath = util["search"](modpath, { "can" }) -- candran.can:356
if not filepath then -- candran.can:348 if not filepath then -- candran.can:357
if _VERSION == "Lua 5.4" then -- candran.can:349 if _VERSION == "Lua 5.4" then -- candran.can:358
return "no candran file in package.path" -- candran.can:350 return "no candran file in package.path" -- candran.can:359
else -- candran.can:350 else -- candran.can:359
return "\ return "\
\9no candran file in package.path" -- candran.can:352 \9no candran file in package.path" -- candran.can:361
end -- candran.can:352 end -- candran.can:361
end -- candran.can:352 end -- candran.can:361
return function(modpath) -- candran.can:355 return function(modpath) -- candran.can:364
local r, s = candran["loadfile"](filepath) -- candran.can:356 local r, s = candran["loadfile"](filepath) -- candran.can:365
if r then -- candran.can:357 if r then -- candran.can:366
return r(modpath, filepath) -- candran.can:358 return r(modpath, filepath) -- candran.can:367
else -- candran.can:358 else -- candran.can:367
error(("error loading candran module '%s' from file '%s':\ error(("error loading candran module '%s' from file '%s':\
\9%s"):format(modpath, filepath, tostring(s)), 0) -- candran.can:360 \9%s"):format(modpath, filepath, tostring(s)), 0) -- candran.can:369
end -- candran.can:360 end -- candran.can:369
end, filepath -- candran.can:362 end, filepath -- candran.can:371
end -- candran.can:362 end -- candran.can:371
candran["setup"] = function() -- candran.can:366 candran["setup"] = function() -- candran.can:375
local searchers = (function() -- candran.can:367 local searchers = (function() -- candran.can:376
if _VERSION == "Lua 5.1" then -- candran.can:367 if _VERSION == "Lua 5.1" then -- candran.can:376
return package["loaders"] -- candran.can:368 return package["loaders"] -- candran.can:377
else -- candran.can:368 else -- candran.can:377
return package["searchers"] -- candran.can:370 return package["searchers"] -- candran.can:379
end -- candran.can:370 end -- candran.can:379
end)() -- candran.can:370 end)() -- candran.can:379
for _, s in ipairs(searchers) do -- candran.can:373 for _, s in ipairs(searchers) do -- candran.can:382
if s == candran["searcher"] then -- candran.can:374 if s == candran["searcher"] then -- candran.can:383
return candran -- candran.can:375 return candran -- candran.can:384
end -- candran.can:375 end -- candran.can:384
end -- candran.can:375 end -- candran.can:384
table["insert"](searchers, 1, candran["searcher"]) -- candran.can:379 table["insert"](searchers, 1, candran["searcher"]) -- candran.can:388
return candran -- candran.can:380 return candran -- candran.can:389
end -- candran.can:380 end -- candran.can:389
return candran -- candran.can:383 return candran -- candran.can:392

163
candran/serpent.lua Normal file
View file

@ -0,0 +1,163 @@
--[[
Serpent source is released under the MIT License
Copyright (c) 2012-2018 Paul Kulchenko (paul@kulchenko.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]
local n, v = "serpent", "0.302" -- (C) 2012-18 Paul Kulchenko; MIT License
local c, d = "Paul Kulchenko", "Lua serializer and pretty printer"
local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'}
local badtype = {thread = true, userdata = true, cdata = true}
local getmetatable = debug and debug.getmetatable or getmetatable
local pairs = function(t) return next, t end -- avoid using __pairs in Lua 5.2+
local keyword, globals, G = {}, {}, (_G or _ENV)
for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false',
'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end
for k,v in pairs(G) do globals[v] = k end -- build func to name mapping
for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do
for k,v in pairs(type(G[g]) == 'table' and G[g] or {}) do globals[v] = g..'.'..k end end
local function s(t, opts)
local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum
local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge
local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge)
local maxlen, metatostring = tonumber(opts.maxlength), opts.metatostring
local iname, comm = '_'..(name or ''), opts.comment and (tonumber(opts.comment) or math.huge)
local numformat = opts.numformat or "%.17g"
local seen, sref, syms, symn = {}, {'local '..iname..'={}'}, {}, 0
local function gensym(val) return '_'..(tostring(tostring(val)):gsub("[^%w]",""):gsub("(%d%w+)",
-- tostring(val) is needed because __tostring may return a non-string value
function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return tostring(syms[s]) end)) end
local function safestr(s) return type(s) == "number" and tostring(huge and snum[tostring(s)] or numformat:format(s))
or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026
or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end
local function comment(s,l) return comm and (l or 0) < comm and ' --[['..select(2, pcall(tostring, s))..']]' or '' end
local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal
and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end
local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r']
local n = name == nil and '' or name
local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n]
local safe = plain and n or '['..safestr(n)..']'
return (path or '')..(plain and path and '.' or '')..safe, safe end
local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(k, o, n) -- k=keys, o=originaltable, n=padding
local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'}
local function padnum(d) return ("%0"..tostring(maxn).."d"):format(tonumber(d)) end
table.sort(k, function(a,b)
-- sort numeric keys first: k[key] is not nil for numerical keys
return (k[a] ~= nil and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum))
< (k[b] ~= nil and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end
local function val2str(t, name, indent, insref, path, plainindex, level)
local ttype, level, mt = type(t), (level or 0), getmetatable(t)
local spath, sname = safename(path, name)
local tag = plainindex and
((type(name) == "number") and '' or name..space..'='..space) or
(name ~= nil and sname..space..'='..space or '')
if seen[t] then -- already seen this element
sref[#sref+1] = spath..space..'='..space..seen[t]
return tag..'nil'..comment('ref', level) end
-- protect from those cases where __tostring may fail
if type(mt) == 'table' and metatostring ~= false then
local to, tr = pcall(function() return mt.__tostring(t) end)
local so, sr = pcall(function() return mt.__serialize(t) end)
if (to or so) then -- knows how to serialize itself
seen[t] = insref or spath
t = so and sr or tr
ttype = type(t)
end -- new value falls through to be serialized
end
if ttype == "table" then
if level >= maxl then return tag..'{}'..comment('maxlvl', level) end
seen[t] = insref or spath
if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty
if maxlen and maxlen < 0 then return tag..'{}'..comment('maxlen', level) end
local maxn, o, out = math.min(#t, maxnum or #t), {}, {}
for key = 1, maxn do o[key] = key end
if not maxnum or #o < maxnum then
local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables
for key in pairs(t) do if o[key] ~= key then n = n + 1; o[n] = key end end end
if maxnum and #o > maxnum then o[maxnum+1] = nil end
if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end
local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output)
for n, key in ipairs(o) do
local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse
if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing
or opts.keyallow and not opts.keyallow[key]
or opts.keyignore and opts.keyignore[key]
or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types
or sparse and value == nil then -- skipping nils; do nothing
elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then
if not seen[key] and not globals[key] then
sref[#sref+1] = 'placeholder'
local sname = safename(iname, gensym(key)) -- iname is table for local variables
sref[#sref] = val2str(key,sname,indent,sname,iname,true) end
sref[#sref+1] = 'placeholder'
local path = seen[t]..'['..tostring(seen[key] or globals[key] or gensym(key))..']'
sref[#sref] = path..space..'='..space..tostring(seen[value] or val2str(value,nil,indent,path))
else
out[#out+1] = val2str(value,key,indent,nil,seen[t],plainindex,level+1)
if maxlen then
maxlen = maxlen - #out[#out]
if maxlen < 0 then break end
end
end
end
local prefix = string.rep(indent or '', level)
local head = indent and '{\n'..prefix..indent or '{'
local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space))
local tail = indent and "\n"..prefix..'}' or '}'
return (custom and custom(tag,head,body,tail,level) or tag..head..body..tail)..comment(t, level)
elseif badtype[ttype] then
seen[t] = insref or spath
return tag..globerr(t, level)
elseif ttype == 'function' then
seen[t] = insref or spath
if opts.nocode then return tag.."function() --[[..skipped..]] end"..comment(t, level) end
local ok, res = pcall(string.dump, t)
local func = ok and "((loadstring or load)("..safestr(res)..",'@serialized'))"..comment(t, level)
return tag..(func or globerr(t, level))
else return tag..safestr(t) end -- handle all other types
end
local sepr = indent and "\n" or ";"..space
local body = val2str(t, name, indent) -- this call also populates sref
local tail = #sref>1 and table.concat(sref, sepr)..sepr or ''
local warn = opts.comment and #sref>1 and space.."--[[incomplete output with shared/self-references skipped]]" or ''
return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end"
end
local function deserialize(data, opts)
local env = (opts and opts.safe == false) and G
or setmetatable({}, {
__index = function(t,k) return t end,
__call = function(t,...) error("cannot call functions") end
})
local f, res = (loadstring or load)('return '..data, nil, nil, env)
if not f then f, res = (loadstring or load)(data, nil, nil, env) end
if not f then return f, res end
if setfenv then setfenv(f, env) end
return pcall(f)
end
local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end
return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s,
load = deserialize,
dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end,
line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end,
block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end }