manen

Fancy Lua REPL
Log | Files | Refs | README | LICENSE

rpc.lua (13529B)


      1 local serpent
      2 do
      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    local n, v = "serpent", "0.303" -- (C) 2012-18 Paul Kulchenko; MIT License
     27    local c, d = "Paul Kulchenko", "Lua serializer and pretty printer"
     28    local snum = {
     29       [tostring(1 / 0)] = '1/0 --[[math.huge]]',
     30       [tostring(-1 / 0)] = '-1/0 --[[-math.huge]]',
     31       [tostring(0 / 0)] = '0/0'
     32    }
     33    local badtype = { thread = true, userdata = true, cdata = true }
     34    local getmetatable = debug and debug.getmetatable or getmetatable
     35    local pairs = function(t) return next, t end -- avoid using __pairs in Lua 5.2+
     36    local keyword, globals, G = {}, {}, (_G or _ENV)
     37    for _, k in ipairs({ 'and', 'break', 'do', 'else', 'elseif', 'end', 'false',
     38       'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
     39       'return', 'then', 'true', 'until', 'while' }) do keyword[k] = true end
     40    for k, v in pairs(G) do globals[v] = k end -- build func to name mapping
     41    for _, g in ipairs({ 'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os' }) do
     42       for k, v in pairs(type(G[g]) == 'table' and G[g] or {}) do globals[v] = g .. '.' .. k end
     43    end
     44 
     45    local function s(t, opts)
     46       local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum
     47       local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge
     48       local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge)
     49       local maxlen, metatostring = tonumber(opts.maxlength), opts.metatostring
     50       local iname, comm = '_' .. (name or ''), opts.comment and (tonumber(opts.comment) or math.huge)
     51       local numformat = opts.numformat or "%.17g"
     52       local seen, sref, syms, symn = {}, { 'local ' .. iname .. '={}' }, {}, 0
     53       local function gensym(val)
     54          return '_' .. (tostring(tostring(val)):gsub("[^%w]", ""):gsub("(%d%w+)",
     55             -- tostring(val) is needed because __tostring may return a non-string value
     56             function(s)
     57                if not syms[s] then
     58                   symn = symn + 1; syms[s] = symn
     59                end
     60                return tostring(syms[s])
     61             end))
     62       end
     63       local function safestr(s)
     64          return type(s) == "number" and (huge and snum[tostring(s)] or numformat:format(s))
     65              or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026
     66              or ("%q"):format(s):gsub("\010", "n"):gsub("\026", "\\026")
     67       end
     68       -- handle radix changes in some locales
     69       if opts.fixradix and (".1f"):format(1.2) ~= "1.2" then
     70          local origsafestr = safestr
     71          safestr = function(s)
     72             return type(s) == "number"
     73                 and (nohuge and snum[tostring(s)] or numformat:format(s):gsub(",", ".")) or origsafestr(s)
     74          end
     75       end
     76       local function comment(s, l)
     77          return comm and (l or 0) < comm and ' --[[' .. select(2, pcall(tostring, s)) .. ']]' or
     78              ''
     79       end
     80       local function globerr(s, l)
     81          return globals[s] and globals[s] .. comment(s, l) or not fatal
     82              and safestr(select(2, pcall(tostring, s))) or error("Can't serialize " .. tostring(s))
     83       end
     84       local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r']
     85          local n = name == nil and '' or name
     86          local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n]
     87          local safe = plain and n or '[' .. safestr(n) .. ']'
     88          return (path or '') .. (plain and path and '.' or '') .. safe, safe
     89       end
     90       local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or
     91           function(k, o, n) -- k=keys, o=originaltable, n=padding
     92              local maxn, to = tonumber(n) or 12, { number = 'a', string = 'b' }
     93              local function padnum(d) return ("%0" .. tostring(maxn) .. "d"):format(tonumber(d)) end
     94              table.sort(k, function(a, b)
     95                 -- sort numeric keys first: k[key] is not nil for numerical keys
     96                 return (k[a] ~= nil and 0 or to[type(a)] or 'z') .. (tostring(a):gsub("%d+", padnum))
     97                     < (k[b] ~= nil and 0 or to[type(b)] or 'z') .. (tostring(b):gsub("%d+", padnum))
     98              end)
     99           end
    100       local function val2str(t, name, indent, insref, path, plainindex, level)
    101          local ttype, level, mt = type(t), (level or 0), getmetatable(t)
    102          local spath, sname = safename(path, name)
    103          local tag = plainindex and
    104              ((type(name) == "number") and '' or name .. space .. '=' .. space) or
    105              (name ~= nil and sname .. space .. '=' .. space or '')
    106          if seen[t] then -- already seen this element
    107             sref[#sref + 1] = spath .. space .. '=' .. space .. seen[t]
    108             return tag .. 'nil' .. comment('ref', level)
    109          end
    110          -- protect from those cases where __tostring may fail
    111          if type(mt) == 'table' and metatostring ~= false then
    112             local to, tr = pcall(function() return mt.__tostring(t) end)
    113             local so, sr = pcall(function() return mt.__serialize(t) end)
    114             if (to or so) then -- knows how to serialize itself
    115                seen[t] = insref or spath
    116                t = so and sr or tr
    117                ttype = type(t)
    118             end -- new value falls through to be serialized
    119          end
    120          if ttype == "table" then
    121             if level >= maxl then return tag .. '{}' .. comment('maxlvl', level) end
    122             seen[t] = insref or spath
    123             if next(t) == nil then return tag .. '{}' .. comment(t, level) end -- table empty
    124             if maxlen and maxlen < 0 then return tag .. '{}' .. comment('maxlen', level) end
    125             local maxn, o, out = math.min(#t, maxnum or #t), {}, {}
    126             for key = 1, maxn do o[key] = key end
    127             if not maxnum or #o < maxnum then
    128                local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables
    129                for key in pairs(t) do
    130                   if o[key] ~= key then
    131                      n = n + 1; o[n] = key
    132                   end
    133                end
    134             end
    135             if maxnum and #o > maxnum then o[maxnum + 1] = nil end
    136             if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end
    137             local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output)
    138             for n, key in ipairs(o) do
    139                local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse
    140                if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing
    141                    or opts.keyallow and not opts.keyallow[key]
    142                    or opts.keyignore and opts.keyignore[key]
    143                    or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types
    144                    or sparse and value == nil then                           -- skipping nils; do nothing
    145                elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then
    146                   if not seen[key] and not globals[key] then
    147                      sref[#sref + 1] = 'placeholder'
    148                      local sname = safename(iname, gensym(key)) -- iname is table for local variables
    149                      sref[#sref] = val2str(key, sname, indent, sname, iname, true)
    150                   end
    151                   sref[#sref + 1] = 'placeholder'
    152                   local path = seen[t] .. '[' .. tostring(seen[key] or globals[key] or gensym(key)) .. ']'
    153                   sref[#sref] = path ..
    154                   space .. '=' .. space .. tostring(seen[value] or val2str(value, nil, indent, path))
    155                else
    156                   out[#out + 1] = val2str(value, key, indent, nil, seen[t], plainindex, level + 1)
    157                   if maxlen then
    158                      maxlen = maxlen - #out[#out]
    159                      if maxlen < 0 then break end
    160                   end
    161                end
    162             end
    163             local prefix = string.rep(indent or '', level)
    164             local head = indent and '{\n' .. prefix .. indent or '{'
    165             local body = table.concat(out, ',' .. (indent and '\n' .. prefix .. indent or space))
    166             local tail = indent and "\n" .. prefix .. '}' or '}'
    167             return (custom and custom(tag, head, body, tail, level) or tag .. head .. body .. tail) .. comment(t, level)
    168          elseif badtype[ttype] then
    169             seen[t] = insref or spath
    170             return tag .. globerr(t, level)
    171          elseif ttype == 'function' then
    172             seen[t] = insref or spath
    173             if opts.nocode then return tag .. "function() --[[..skipped..]] end" .. comment(t, level) end
    174             local ok, res = pcall(string.dump, t)
    175             local func = ok and "((loadstring or load)(" .. safestr(res) .. ",'@serialized'))" .. comment(t, level)
    176             return tag .. (func or globerr(t, level))
    177          else
    178             return tag .. safestr(t)
    179          end -- handle all other types
    180       end
    181       local sepr = indent and "\n" or ";" .. space
    182       local body = val2str(t, name, indent) -- this call also populates sref
    183       local tail = #sref > 1 and table.concat(sref, sepr) .. sepr or ''
    184       local warn = opts.comment and #sref > 1 and space .. "--[[incomplete output with shared/self-references skipped]]" or
    185           ''
    186       return not name and body .. warn or "do local " .. body .. sepr .. tail .. "return " .. name .. sepr .. "end"
    187    end
    188 
    189    local function deserialize(data, opts)
    190       local env = (opts and opts.safe == false) and G
    191           or setmetatable({}, {
    192              __index = function(t, k) return t end,
    193              __call = function(t, ...) error("cannot call functions") end
    194           })
    195       local f, res = (loadstring or load)('return ' .. data, nil, nil, env)
    196       if not f then f, res = (loadstring or load)(data, nil, nil, env) end
    197       if not f then return f, res end
    198       if setfenv then setfenv(f, env) end
    199       return pcall(f)
    200    end
    201 
    202    local function merge(a, b)
    203       if b then for k, v in pairs(b) do a[k] = v end end; return a;
    204    end
    205 
    206    serpent = {
    207       _NAME = n,
    208       _COPYRIGHT = c,
    209       _DESCRIPTION = d,
    210       _VERSION = v,
    211       serialize = s,
    212       load = deserialize,
    213       dump = function(a, opts) return s(a, merge({ name = '_', compact = true, sparse = true }, opts)) end,
    214       line = function(a, opts) return s(a, merge({ sortkeys = true, comment = true }, opts)) end,
    215       block = function(a, opts) return s(a, merge({ indent = '  ', sortkeys = true, comment = true }, opts)) end
    216    }
    217 end
    218 
    219 local rpc = {}
    220 
    221 function rpc.respond(command, data)
    222    io.write(serpent.dump({
    223       ty = type(data),
    224       data = data,
    225       command = command
    226    }, { metatostring = false }))
    227    io.write('\n')
    228    io.flush()
    229 end
    230 
    231 function rpc.globals()
    232    rpc.respond('globals', _G)
    233 end
    234 
    235 local load_fn = _VERSION == 'Lua 5.1' and loadstring or load
    236 
    237 function rpc.exec(code)
    238    code = load_fn('return ' .. code)()
    239 
    240    local fn = load_fn(code, 'repl')
    241 
    242    if not fn then
    243       fn = assert(load_fn('return (' .. code .. ')', 'repl'))
    244    end
    245 
    246    local function cleanup()
    247       if debug and debug.sethook then
    248          debug.sethook()
    249       end
    250 
    251       if io and io.open and rpc.cancel_file then
    252          local f = io.open(rpc.cancel_file, 'w')
    253 
    254          if f then
    255             f:close()
    256          end
    257       end
    258    end
    259 
    260    if debug and debug.sethook and io and io.open and rpc.cancel_file then
    261       local function cancel()
    262          local f = assert(io.open(rpc.cancel_file, 'r'))
    263 
    264          local data = f:read('*a')
    265 
    266          f:close()
    267 
    268          if data:find('stop') then
    269             cleanup()
    270             error('cancelled')
    271          end
    272       end
    273 
    274       -- we can't do every line like in MluaExecutor due to the FS call
    275       debug.sethook(cancel, "", 500000)
    276    end
    277 
    278    local success, res = pcall(fn)
    279 
    280    cleanup()
    281 
    282    if success then
    283       rpc.respond('exec', res)
    284    else
    285       rpc.respond('error', res)
    286    end
    287 end
    288 
    289 function rpc.prepare(file)
    290    -- LuaJIT can't stop due to JIT
    291    if not io or jit then
    292       rpc.respond('cancel', false)
    293       return
    294    end
    295 
    296    rpc.cancel_file = file
    297 
    298    rpc.respond('cancel', true)
    299 end
    300 
    301 for line in io.stdin:lines() do
    302    local cmd, arg = line:match('(.-):(.*)')
    303    if cmd == nil then
    304       cmd = line
    305    end
    306 
    307    local success, err = pcall(rpc[cmd], arg)
    308 
    309    if not success then
    310       rpc.respond('error', err)
    311    end
    312 end