core refactoring
This commit is contained in:
parent
49fa684f47
commit
f15c139502
19 changed files with 114 additions and 91 deletions
101
etc/api/api.lua
Normal file
101
etc/api/api.lua
Normal file
|
@ -0,0 +1,101 @@
|
|||
--[[ API Library
|
||||
-- (c) Er2 2021 <er2@dismail.de>
|
||||
-- Zlib License
|
||||
--]]
|
||||
|
||||
local tools =require 'etc.api.tools'
|
||||
local json = require 'etc.json'
|
||||
local events=require 'etc.events'
|
||||
local api = { _ev_ = {} }
|
||||
api.__index = api -- Make class
|
||||
|
||||
events(api) -- inheritance
|
||||
|
||||
function api:request(...) return tools.request(self.token, ...) end
|
||||
|
||||
-- Getters without params
|
||||
|
||||
function api:getMe() return self:request 'getMe' end
|
||||
function api:getMyCommands() return self:request 'getMyCommands' end
|
||||
|
||||
-- Getters with params
|
||||
|
||||
function api:getChat(cid) return self:request('getChat', {chat_id = cid}) end
|
||||
|
||||
-- Setters
|
||||
|
||||
function api:send(msg, txt, pmod, dwp, dnot, rtmid, rmp)
|
||||
rmp = type(rmp) == 'table' and json.encode(rmp) or rmp
|
||||
msg = (type(msg) == 'table' and msg.chat and msg.chat.id) and msg.chat.id or msg
|
||||
pmod = (type(pmod) == 'boolean' and pmod == true) and 'markdown' or pmod
|
||||
if dwp == nil then dwp = true end
|
||||
|
||||
if txt and #txt > 4096 then
|
||||
txt = txt:sub(0, 4092) .. '...'
|
||||
end
|
||||
|
||||
return self:request('sendMessage', {
|
||||
chat_id = msg,
|
||||
text = txt,
|
||||
parse_mode = pmod,
|
||||
disable_web_page_preview = dwp,
|
||||
disable_notification = dnot,
|
||||
reply_to_message_id = rtmid,
|
||||
reply_markup = rmp,
|
||||
})
|
||||
end
|
||||
|
||||
function api:reply(msg, txt, pmod, dwp, rmp, dnot)
|
||||
if type(msg) ~= 'table' or not msg.chat or not msg.chat.id or not msg.message_id then return false end
|
||||
rmp = type(rmp) == 'table' and json.encode(rmp) or rmp
|
||||
pmod = (type(pmod) == 'boolean' and pmod == true) and 'markdown' or pmod
|
||||
|
||||
return self:request('sendMessage', {
|
||||
chat_id = msg.chat.id,
|
||||
text = txt,
|
||||
parse_mode = pmod,
|
||||
disable_web_page_preview = dwp,
|
||||
disable_notification = dnot,
|
||||
reply_to_message_id = msg.message_id,
|
||||
reply_markup = rmp,
|
||||
})
|
||||
end
|
||||
|
||||
function api:forward(cid, frcid, mid, dnot)
|
||||
return self:request('forwardMessage', {
|
||||
chat_id = cid,
|
||||
from_chat_id = frcid,
|
||||
disable_notification = dnot,
|
||||
message_id = mid,
|
||||
})
|
||||
end
|
||||
|
||||
function api:sendPoll(cid, q, opt, anon, ptype, mansw, coptid, expl, pmode, oper, cdate, closed, dnot, rtmid, rmp)
|
||||
opt = type(opt) == 'string' and opt or json.encode(opt)
|
||||
rmp = type(rmp) == 'table' and json.encode(rmp) or rmp
|
||||
anon = type(anon) == 'boolean' and anon or false
|
||||
mansw = type(mansw) == 'boolean' and mansw or false
|
||||
return self:request('sendPoll', {
|
||||
chat_id = cid,
|
||||
question = q,
|
||||
options = opt,
|
||||
is_anonymous = anon,
|
||||
type = ptype,
|
||||
allows_multiple_answers = mansw,
|
||||
correct_option_id = coptid,
|
||||
explanation = expl,
|
||||
explanation_parse_mode = pmode,
|
||||
open_period = oper,
|
||||
close_date = cdate,
|
||||
is_closed = closed,
|
||||
disable_notification = dnot,
|
||||
reply_to_message_id = rtmid,
|
||||
reply_markup = rmp,
|
||||
})
|
||||
end
|
||||
|
||||
function api:setMyCommands(cmds)
|
||||
return self:request('setMyCommands', { commands = json.encode(cmds) })
|
||||
end
|
||||
|
||||
return api
|
125
etc/api/core.lua
Normal file
125
etc/api/core.lua
Normal file
|
@ -0,0 +1,125 @@
|
|||
--[[ Core file
|
||||
-- (c) Er2 2021 <er2@dismail.de>
|
||||
-- Zlib License
|
||||
--]]
|
||||
|
||||
local tools = require 'etc.api.tools'
|
||||
local api = require 'etc.api.api'
|
||||
api.__index = api
|
||||
|
||||
function api:_ev(t, i, name, ...)
|
||||
local v = t[i]
|
||||
if v.name == name then
|
||||
v.fn(self, ...)
|
||||
if v.type == 'once' then table.remove(t, i) end
|
||||
end
|
||||
end
|
||||
|
||||
-- UPDATES --
|
||||
function api:getUpdates(lim, offs, tout, allowed)
|
||||
allowed = type(allowed) == 'table' and tools.json.encode(allowed) or allowed
|
||||
return self:request('getUpdates', {
|
||||
timeout = tout,
|
||||
offset = offs,
|
||||
limit = lim,
|
||||
allowed_updates = allowed
|
||||
})
|
||||
end
|
||||
|
||||
local function receiveUpdate(self, update)
|
||||
if update then self:emit('update', update) end
|
||||
|
||||
if update.message then
|
||||
local txt = update.message.text
|
||||
local cmd, to = tools.fetchCmd(txt)
|
||||
if cmd and (not to or to == self.info.username) then
|
||||
local args = {}
|
||||
txt = txt:sub(#cmd + #(to or {}) + 3)
|
||||
for s in txt:gmatch '%S+' do table.insert(args, s) end
|
||||
|
||||
update.message.cmd = cmd
|
||||
update.message.args = args
|
||||
|
||||
return self:emit('command', update.message)
|
||||
elseif cmd then return end
|
||||
|
||||
self:emit('message', update.message)
|
||||
|
||||
elseif update.edited_message then
|
||||
self:emit('messageEdit', update.edited_message)
|
||||
|
||||
elseif update.channel_post then self:emit('channelPost', update.channel_post)
|
||||
elseif update.edited_channel_post then self:emit('channelPostEdit', update.edited_channel_post)
|
||||
|
||||
elseif update.poll then self:emit('poll', update.poll)
|
||||
elseif update.poll_answer then self:emit('pollAnswer', update.poll_answer)
|
||||
|
||||
elseif update.callback_query then self:emit('callbackQuery', update.callback_query)
|
||||
elseif update.inline_query then self:emit('inlineQuery', update.inline_query)
|
||||
elseif update.shipping_query then self:emit('shippingQuery', update.shipping_query)
|
||||
elseif update.pre_checkout_query then self:emit('preCheckoutQuery', update.pre_checkout_query)
|
||||
|
||||
elseif update.chosen_inline_result then self:emit('inlineResult', update.chosen_inline_result)
|
||||
end
|
||||
end
|
||||
|
||||
function api:_getUpd(lim, offs, ...)
|
||||
local u, ok = self:getUpdates(lim, offs, ...)
|
||||
if not ok or not u or (u and type(u) ~= 'table') or not u.result then return end
|
||||
for _, v in pairs(u.result) do
|
||||
offs = v.update_id + 1
|
||||
receiveUpdate(self, v)
|
||||
end
|
||||
return offs
|
||||
end
|
||||
|
||||
function api:_loop(lim, offs, ...)
|
||||
while api.runs do
|
||||
local o = self:_getUpd(lim, offs, ...)
|
||||
offs = o and o or offs
|
||||
end
|
||||
self:getUpdates(lim, offs, ...)
|
||||
end
|
||||
|
||||
-- RUN --
|
||||
function api:run(lim, offs, tout, al)
|
||||
lim = tonumber(lim) or 1
|
||||
offs = tonumber(offs) or 0
|
||||
tout = tonumber(tout) or 0
|
||||
|
||||
self.runs = true
|
||||
self:emit('ready')
|
||||
|
||||
self.co = coroutine.create(api._loop)
|
||||
coroutine.resume(self.co, self, lim, tout, offs, al)
|
||||
end
|
||||
|
||||
function api:destroy() self.runs = false end
|
||||
|
||||
function api:login(token, thn)
|
||||
self.token = assert(token or self.token, 'Provide token!')
|
||||
|
||||
repeat
|
||||
local r, o = self:getMe()
|
||||
if o and r then self.info = r end
|
||||
until (self.info or {}).result
|
||||
|
||||
self.info = self.info.result
|
||||
self.info.name = self.info.first_name
|
||||
|
||||
if type(thn) == 'function' then thn(self) end
|
||||
|
||||
if not self.nr then self:run() end
|
||||
end
|
||||
|
||||
return function(opts)
|
||||
if not token or type(token) ~= 'string' then token = nil end
|
||||
|
||||
local self = setmetatable({}, api)
|
||||
if type(opts) == 'table' then
|
||||
if opts.token then self.token = opts.token end
|
||||
if opts.norun then self.nr = true end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
1
etc/api/init.lua
Normal file
1
etc/api/init.lua
Normal file
|
@ -0,0 +1 @@
|
|||
return require 'etc.api.core'
|
70
etc/api/tools.lua
Normal file
70
etc/api/tools.lua
Normal file
|
@ -0,0 +1,70 @@
|
|||
--[[ Additional tools
|
||||
-- (c) Er2 2021 <er2@dismail.de>
|
||||
-- Zlib license
|
||||
--]]
|
||||
|
||||
local tools = {
|
||||
json = require 'etc.json',
|
||||
}
|
||||
|
||||
local json = tools.json
|
||||
local https = require 'ssl.https'
|
||||
local ltn12 = require 'ltn12'
|
||||
|
||||
function tools.fetchCmd(text)
|
||||
return
|
||||
text:match '/([%w_]+)', -- cmd
|
||||
text:match '/[%w_]+@([%w_]+)' -- to
|
||||
end
|
||||
|
||||
-- https://gist.github.com/liukun/f9ce7d6d14fa45fe9b924a3eed5c3d99
|
||||
function tools.urlencode(url)
|
||||
if url == nil then return end
|
||||
url = url:gsub("\n", "\r\n")
|
||||
url = url:gsub("([^%w_%- . ~])", function(c) return string.format("%%%02X", string.byte(c)) end)
|
||||
url = url:gsub(" ", "+")
|
||||
return url
|
||||
end
|
||||
|
||||
function tools.req(url, par)
|
||||
if type(par) == 'table' then
|
||||
url = url .. '?'
|
||||
for k,v in pairs(par) do
|
||||
url = url ..'&'.. k ..'='.. tools.urlencode(tostring(v))
|
||||
end
|
||||
end
|
||||
|
||||
local resp = {}
|
||||
local succ, res = https.request {
|
||||
url = url,
|
||||
method = 'GET',
|
||||
sink = ltn12.sink.table(resp),
|
||||
}
|
||||
|
||||
if not succ then
|
||||
print('Connection error [' .. res .. ']')
|
||||
return nil, false
|
||||
end
|
||||
return resp[1], true
|
||||
end
|
||||
|
||||
function tools.requ(url, par, dbg)
|
||||
local res, succ = tools.req(url, par)
|
||||
if dbg then print(url, succ, res) end
|
||||
res = json.decode(res or '{}')
|
||||
if not succ or not res then return {}, false end
|
||||
return res, true
|
||||
end
|
||||
|
||||
function tools.request(token, endpoint, param, dbg)
|
||||
assert(token, 'Provide token!')
|
||||
assert(endpoint, 'Provide endpoint!')
|
||||
|
||||
local url = 'https://api.telegram.org/bot' .. token .. '/' .. endpoint
|
||||
|
||||
-- dbg = true
|
||||
local resp = tools.requ(url, param, dbg)
|
||||
return resp, resp.ok or false
|
||||
end
|
||||
|
||||
return tools
|
4
etc/db/.gitignore
vendored
Normal file
4
etc/db/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
*
|
||||
!.gitignore
|
||||
!init.lua
|
||||
!mp.lua
|
73
etc/db/init.lua
Normal file
73
etc/db/init.lua
Normal file
|
@ -0,0 +1,73 @@
|
|||
local mp = require 'etc.db.mp'
|
||||
|
||||
local tools = {
|
||||
isFile = function(path)
|
||||
local f = io.open(path, 'r')
|
||||
if not f then return false end
|
||||
f:close()
|
||||
return true
|
||||
end,
|
||||
isDir = function(path)
|
||||
path = (path .. '/'):gsub('//', '/')
|
||||
local o, _, c = os.rename(path, path)
|
||||
return o or c == 13
|
||||
end,
|
||||
|
||||
loadPg = function(db, k)
|
||||
local f = io.open(db._path .. '/' .. k, 'rb')
|
||||
if not f then return end
|
||||
|
||||
local res = mp.unpack(f:read '*a')
|
||||
f:close()
|
||||
return res
|
||||
end,
|
||||
|
||||
savePg = function(db, k, page)
|
||||
if type(k) == 'string' and k:sub(1, 1) == '_' then return '_' end
|
||||
local f = io.open(db._path .. '/' .. k, 'wb')
|
||||
if type(page) ~= 'table' or not f then return false end
|
||||
|
||||
f:write(mp.pack(page))
|
||||
f:close()
|
||||
return true
|
||||
end,
|
||||
}
|
||||
|
||||
local dbInt = {
|
||||
save = function(db, p)
|
||||
if p then
|
||||
if type(p) ~= 'string' or type(db[p]) ~= 'table' then return false end
|
||||
return tools.savePg(db, p, db[p])
|
||||
end
|
||||
for p, con in pairs(db) do
|
||||
if not tools.savePg(db, p, con) then return false end
|
||||
end
|
||||
return true
|
||||
end,
|
||||
}
|
||||
|
||||
local _db = {
|
||||
__index = function(db, k)
|
||||
if dbInt[k] then return dbInt[k] end
|
||||
if tools.isFile(db._path .. '/' .. k) then
|
||||
db[k] = tools.loadPg(db, k)
|
||||
end
|
||||
return rawget(db, k)
|
||||
end,
|
||||
__newindex = function(db, k, v)
|
||||
if type(k) ~= 'string' or type(v) == 'function' then return end
|
||||
if k:sub(1, 1) == '_' then return end
|
||||
return rawset(db, k, v)
|
||||
end
|
||||
}
|
||||
|
||||
return setmetatable({}, {
|
||||
__mode = 'kv',
|
||||
__call = function(self, path)
|
||||
assert(tools.isDir(path), path .. ' is not a directory')
|
||||
if self[path] then return self[path] end
|
||||
local db = setmetatable({_path = path}, _db)
|
||||
self[path] = db
|
||||
return db
|
||||
end
|
||||
})
|
634
etc/db/mp.lua
Normal file
634
etc/db/mp.lua
Normal file
|
@ -0,0 +1,634 @@
|
|||
-- lua-MessagePack <https://fperrad.frama.io/lua-MessagePack/>
|
||||
-- MIT/X11 License
|
||||
|
||||
local char = require'string'.char
|
||||
local format = require'string'.format
|
||||
local math_type = require'math'.type
|
||||
local tointeger = require'math'.tointeger
|
||||
local tconcat = require'table'.concat
|
||||
local pack = require'string'.pack
|
||||
local unpack = require'string'.unpack
|
||||
local m = {}
|
||||
|
||||
--[[ debug only
|
||||
local function hexadump (s)
|
||||
return (s:gsub('.', function (c) return format('%02X ', c:byte()) end))
|
||||
end
|
||||
m.hexadump = hexadump
|
||||
--]]
|
||||
|
||||
local function argerror (caller, narg, extramsg)
|
||||
error("bad argument #" .. tostring(narg) .. " to "
|
||||
.. caller .. " (" .. extramsg .. ")")
|
||||
end
|
||||
|
||||
local function typeerror (caller, narg, arg, tname)
|
||||
argerror(caller, narg, tname .. " expected, got " .. type(arg))
|
||||
end
|
||||
|
||||
local function checktype (caller, narg, arg, tname)
|
||||
if type(arg) ~= tname then
|
||||
typeerror(caller, narg, arg, tname)
|
||||
end
|
||||
end
|
||||
|
||||
local packers = setmetatable({}, {
|
||||
__index = function (t, k)
|
||||
if k == 1 then return end -- allows ipairs
|
||||
error("pack '" .. k .. "' is unimplemented")
|
||||
end
|
||||
})
|
||||
m.packers = packers
|
||||
|
||||
packers['nil'] = function (buffer)
|
||||
buffer[#buffer+1] = char(0xC0) -- nil
|
||||
end
|
||||
|
||||
packers['boolean'] = function (buffer, bool)
|
||||
if bool then
|
||||
buffer[#buffer+1] = char(0xC3) -- true
|
||||
else
|
||||
buffer[#buffer+1] = char(0xC2) -- false
|
||||
end
|
||||
end
|
||||
|
||||
packers['string_compat'] = function (buffer, str)
|
||||
local n = #str
|
||||
if n <= 0x1F then
|
||||
buffer[#buffer+1] = char(0xA0 + n) -- fixstr
|
||||
elseif n <= 0xFFFF then
|
||||
buffer[#buffer+1] = pack('>B I2', 0xDA, n) -- str16
|
||||
elseif n <= 0xFFFFFFFF.0 then
|
||||
buffer[#buffer+1] = pack('>B I4', 0xDB, n) -- str32
|
||||
else
|
||||
error"overflow in pack 'string_compat'"
|
||||
end
|
||||
buffer[#buffer+1] = str
|
||||
end
|
||||
|
||||
packers['_string'] = function (buffer, str)
|
||||
local n = #str
|
||||
if n <= 0x1F then
|
||||
buffer[#buffer+1] = char(0xA0 + n) -- fixstr
|
||||
elseif n <= 0xFF then
|
||||
buffer[#buffer+1] = char(0xD9, n) -- str8
|
||||
elseif n <= 0xFFFF then
|
||||
buffer[#buffer+1] = pack('>B I2', 0xDA, n) -- str16
|
||||
elseif n <= 0xFFFFFFFF.0 then
|
||||
buffer[#buffer+1] = pack('>B I4', 0xDB, n) -- str32
|
||||
else
|
||||
error"overflow in pack 'string'"
|
||||
end
|
||||
buffer[#buffer+1] = str
|
||||
end
|
||||
|
||||
packers['binary'] = function (buffer, str)
|
||||
local n = #str
|
||||
if n <= 0xFF then
|
||||
buffer[#buffer+1] = char(0xC4, n) -- bin8
|
||||
elseif n <= 0xFFFF then
|
||||
buffer[#buffer+1] = pack('>B I2', 0xC5, n) -- bin16
|
||||
elseif n <= 0xFFFFFFFF.0 then
|
||||
buffer[#buffer+1] = pack('>B I4', 0xC6, n) -- bin32
|
||||
else
|
||||
error"overflow in pack 'binary'"
|
||||
end
|
||||
buffer[#buffer+1] = str
|
||||
end
|
||||
|
||||
local set_string = function (str)
|
||||
if str == 'string_compat' then
|
||||
packers['string'] = packers['string_compat']
|
||||
elseif str == 'string' then
|
||||
packers['string'] = packers['_string']
|
||||
elseif str == 'binary' then
|
||||
packers['string'] = packers['binary']
|
||||
else
|
||||
argerror('set_string', 1, "invalid option '" .. str .."'")
|
||||
end
|
||||
end
|
||||
m.set_string = set_string
|
||||
|
||||
packers['map'] = function (buffer, tbl, n)
|
||||
if n <= 0x0F then
|
||||
buffer[#buffer+1] = char(0x80 + n) -- fixmap
|
||||
elseif n <= 0xFFFF then
|
||||
buffer[#buffer+1] = pack('>B I2', 0xDE, n) -- map16
|
||||
elseif n <= 0xFFFFFFFF.0 then
|
||||
buffer[#buffer+1] = pack('>B I4', 0xDF, n) -- map32
|
||||
else
|
||||
error"overflow in pack 'map'"
|
||||
end
|
||||
for k, v in pairs(tbl) do
|
||||
packers[type(k)](buffer, k)
|
||||
packers[type(v)](buffer, v)
|
||||
end
|
||||
end
|
||||
|
||||
packers['array'] = function (buffer, tbl, n)
|
||||
if n <= 0x0F then
|
||||
buffer[#buffer+1] = char(0x90 + n) -- fixarray
|
||||
elseif n <= 0xFFFF then
|
||||
buffer[#buffer+1] = pack('>B I2', 0xDC, n) -- array16
|
||||
elseif n <= 0xFFFFFFFF.0 then
|
||||
buffer[#buffer+1] = pack('>B I4', 0xDD, n) -- array32
|
||||
else
|
||||
error"overflow in pack 'array'"
|
||||
end
|
||||
for i = 1, n do
|
||||
local v = tbl[i]
|
||||
packers[type(v)](buffer, v)
|
||||
end
|
||||
end
|
||||
|
||||
local set_array = function (array)
|
||||
if array == 'without_hole' then
|
||||
packers['_table'] = function (buffer, tbl)
|
||||
local is_map, n, max = false, 0, 0
|
||||
for k in pairs(tbl) do
|
||||
if type(k) == 'number' and k > 0 then
|
||||
if k > max then
|
||||
max = k
|
||||
end
|
||||
else
|
||||
is_map = true
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if max ~= n then -- there are holes
|
||||
is_map = true
|
||||
end
|
||||
if is_map then
|
||||
packers['map'](buffer, tbl, n)
|
||||
else
|
||||
packers['array'](buffer, tbl, n)
|
||||
end
|
||||
end
|
||||
elseif array == 'with_hole' then
|
||||
packers['_table'] = function (buffer, tbl)
|
||||
local is_map, n, max = false, 0, 0
|
||||
for k in pairs(tbl) do
|
||||
if type(k) == 'number' and k > 0 then
|
||||
if k > max then
|
||||
max = k
|
||||
end
|
||||
else
|
||||
is_map = true
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if is_map then
|
||||
packers['map'](buffer, tbl, n)
|
||||
else
|
||||
packers['array'](buffer, tbl, max)
|
||||
end
|
||||
end
|
||||
elseif array == 'always_as_map' then
|
||||
packers['_table'] = function(buffer, tbl)
|
||||
local n = 0
|
||||
for k in pairs(tbl) do
|
||||
n = n + 1
|
||||
end
|
||||
packers['map'](buffer, tbl, n)
|
||||
end
|
||||
else
|
||||
argerror('set_array', 1, "invalid option '" .. array .."'")
|
||||
end
|
||||
end
|
||||
m.set_array = set_array
|
||||
|
||||
packers['table'] = function (buffer, tbl)
|
||||
packers['_table'](buffer, tbl)
|
||||
end
|
||||
|
||||
packers['unsigned'] = function (buffer, n)
|
||||
if n >= 0 then
|
||||
if n <= 0x7F then
|
||||
buffer[#buffer+1] = char(n) -- fixnum_pos
|
||||
elseif n <= 0xFF then
|
||||
buffer[#buffer+1] = char(0xCC, n) -- uint8
|
||||
elseif n <= 0xFFFF then
|
||||
buffer[#buffer+1] = pack('>B I2', 0xCD, n) -- uint16
|
||||
elseif n <= 0xFFFFFFFF.0 then
|
||||
buffer[#buffer+1] = pack('>B I4', 0xCE, n) -- uint32
|
||||
else
|
||||
buffer[#buffer+1] = pack('>B I8', 0xCF, n) -- uint64
|
||||
end
|
||||
else
|
||||
if n >= -0x20 then
|
||||
buffer[#buffer+1] = char(0x100 + n) -- fixnum_neg
|
||||
elseif n >= -0x80 then
|
||||
buffer[#buffer+1] = pack('>B i1', 0xD0, n) -- int8
|
||||
elseif n >= -0x8000 then
|
||||
buffer[#buffer+1] = pack('>B i2', 0xD1, n) -- int16
|
||||
elseif n >= -0x80000000 then
|
||||
buffer[#buffer+1] = pack('>B i4', 0xD2, n) -- int32
|
||||
else
|
||||
buffer[#buffer+1] = pack('>B i8', 0xD3, n) -- int64
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
packers['signed'] = function (buffer, n)
|
||||
if n >= 0 then
|
||||
if n <= 0x7F then
|
||||
buffer[#buffer+1] = char(n) -- fixnum_pos
|
||||
elseif n <= 0x7FFF then
|
||||
buffer[#buffer+1] = pack('>B i2', 0xD1, n) -- int16
|
||||
elseif n <= 0x7FFFFFFF then
|
||||
buffer[#buffer+1] = pack('>B i4', 0xD2, n) -- int32
|
||||
else
|
||||
buffer[#buffer+1] = pack('>B i8', 0xD3, n) -- int64
|
||||
end
|
||||
else
|
||||
if n >= -0x20 then
|
||||
buffer[#buffer+1] = char(0xE0 + 0x20 + n) -- fixnum_neg
|
||||
elseif n >= -0x80 then
|
||||
buffer[#buffer+1] = pack('>B i1', 0xD0, n) -- int8
|
||||
elseif n >= -0x8000 then
|
||||
buffer[#buffer+1] = pack('>B i2', 0xD1, n) -- int16
|
||||
elseif n >= -0x80000000 then
|
||||
buffer[#buffer+1] = pack('>B i4', 0xD2, n) -- int32
|
||||
else
|
||||
buffer[#buffer+1] = pack('>B i8', 0xD3, n) -- int64
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local set_integer = function (integer)
|
||||
if integer == 'unsigned' then
|
||||
packers['integer'] = packers['unsigned']
|
||||
elseif integer == 'signed' then
|
||||
packers['integer'] = packers['signed']
|
||||
else
|
||||
argerror('set_integer', 1, "invalid option '" .. integer .."'")
|
||||
end
|
||||
end
|
||||
m.set_integer = set_integer
|
||||
|
||||
packers['float'] = function (buffer, n)
|
||||
buffer[#buffer+1] = pack('>B f', 0xCA, n)
|
||||
end
|
||||
|
||||
packers['double'] = function (buffer, n)
|
||||
buffer[#buffer+1] = pack('>B d', 0xCB, n)
|
||||
end
|
||||
|
||||
local set_number = function (number)
|
||||
if number == 'float' then
|
||||
packers['number'] = function (buffer, n)
|
||||
if math_type(n) == 'integer' then
|
||||
packers['integer'](buffer, n)
|
||||
else
|
||||
packers['float'](buffer, n)
|
||||
end
|
||||
end
|
||||
elseif number == 'double' then
|
||||
packers['number'] = function (buffer, n)
|
||||
if math_type(n) == 'integer' then
|
||||
packers['integer'](buffer, n)
|
||||
else
|
||||
packers['double'](buffer, n)
|
||||
end
|
||||
end
|
||||
else
|
||||
argerror('set_number', 1, "invalid option '" .. number .."'")
|
||||
end
|
||||
end
|
||||
m.set_number = set_number
|
||||
|
||||
for k = 0, 4 do
|
||||
local n = tointeger(2^k)
|
||||
local fixext = 0xD4 + k
|
||||
packers['fixext' .. tostring(n)] = function (buffer, tag, data)
|
||||
assert(#data == n, "bad length for fixext" .. tostring(n))
|
||||
buffer[#buffer+1] = pack('>B i1', fixext, tag)
|
||||
buffer[#buffer+1] = data
|
||||
end
|
||||
end
|
||||
|
||||
packers['ext'] = function (buffer, tag, data)
|
||||
local n = #data
|
||||
if n <= 0xFF then
|
||||
buffer[#buffer+1] = pack('>B B i1', 0xC7, n, tag) -- ext8
|
||||
elseif n <= 0xFFFF then
|
||||
buffer[#buffer+1] = pack('>B I2 i1', 0xC8, n, tag) -- ext16
|
||||
elseif n <= 0xFFFFFFFF.0 then
|
||||
buffer[#buffer+1] = pack('>B I4 i1', 0xC9, n, tag) -- ext32
|
||||
else
|
||||
error"overflow in pack 'ext'"
|
||||
end
|
||||
buffer[#buffer+1] = data
|
||||
end
|
||||
|
||||
function m.pack (data)
|
||||
local buffer = {}
|
||||
packers[type(data)](buffer, data)
|
||||
return tconcat(buffer)
|
||||
end
|
||||
|
||||
|
||||
local unpackers -- forward declaration
|
||||
|
||||
local function unpack_cursor (c)
|
||||
local s, i, j = c.s, c.i, c.j
|
||||
if i > j then
|
||||
c:underflow(i)
|
||||
s, i, j = c.s, c.i, c.j
|
||||
end
|
||||
local val = s:byte(i)
|
||||
c.i = i+1
|
||||
return unpackers[val](c, val)
|
||||
end
|
||||
m.unpack_cursor = unpack_cursor
|
||||
|
||||
local function unpack_str (c, n)
|
||||
local s, i, j = c.s, c.i, c.j
|
||||
local e = i+n-1
|
||||
if e > j or n < 0 then
|
||||
c:underflow(e)
|
||||
s, i, j = c.s, c.i, c.j
|
||||
e = i+n-1
|
||||
end
|
||||
c.i = i+n
|
||||
return s:sub(i, e)
|
||||
end
|
||||
|
||||
local function unpack_array (c, n)
|
||||
local t = {}
|
||||
for i = 1, n do
|
||||
t[i] = unpack_cursor(c)
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
local function unpack_map (c, n)
|
||||
local t = {}
|
||||
for i = 1, n do
|
||||
local k = unpack_cursor(c)
|
||||
local val = unpack_cursor(c)
|
||||
if k == nil or k ~= k then
|
||||
k = m.sentinel
|
||||
end
|
||||
if k ~= nil then
|
||||
t[k] = val
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
local function unpack_float (c)
|
||||
local s, i, j = c.s, c.i, c.j
|
||||
if i+3 > j then
|
||||
c:underflow(i+3)
|
||||
s, i, j = c.s, c.i, c.j
|
||||
end
|
||||
c.i = i+4
|
||||
return unpack('>f', s, i)
|
||||
end
|
||||
|
||||
local function unpack_double (c)
|
||||
local s, i, j = c.s, c.i, c.j
|
||||
if i+7 > j then
|
||||
c:underflow(i+7)
|
||||
s, i, j = c.s, c.i, c.j
|
||||
end
|
||||
c.i = i+8
|
||||
return unpack('>d', s, i)
|
||||
end
|
||||
|
||||
local function unpack_uint8 (c)
|
||||
local s, i, j = c.s, c.i, c.j
|
||||
if i > j then
|
||||
c:underflow(i)
|
||||
s, i, j = c.s, c.i, c.j
|
||||
end
|
||||
c.i = i+1
|
||||
return unpack('>I1', s, i)
|
||||
end
|
||||
|
||||
local function unpack_uint16 (c)
|
||||
local s, i, j = c.s, c.i, c.j
|
||||
if i+1 > j then
|
||||
c:underflow(i+1)
|
||||
s, i, j = c.s, c.i, c.j
|
||||
end
|
||||
c.i = i+2
|
||||
return unpack('>I2', s, i)
|
||||
end
|
||||
|
||||
local function unpack_uint32 (c)
|
||||
local s, i, j = c.s, c.i, c.j
|
||||
if i+3 > j then
|
||||
c:underflow(i+3)
|
||||
s, i, j = c.s, c.i, c.j
|
||||
end
|
||||
c.i = i+4
|
||||
return unpack('>I4', s, i)
|
||||
end
|
||||
|
||||
local function unpack_uint64 (c)
|
||||
local s, i, j = c.s, c.i, c.j
|
||||
if i+7 > j then
|
||||
c:underflow(i+7)
|
||||
s, i, j = c.s, c.i, c.j
|
||||
end
|
||||
c.i = i+8
|
||||
return unpack('>I8', s, i)
|
||||
end
|
||||
|
||||
local function unpack_int8 (c)
|
||||
local s, i, j = c.s, c.i, c.j
|
||||
if i > j then
|
||||
c:underflow(i)
|
||||
s, i, j = c.s, c.i, c.j
|
||||
end
|
||||
c.i = i+1
|
||||
return unpack('>i1', s, i)
|
||||
end
|
||||
|
||||
local function unpack_int16 (c)
|
||||
local s, i, j = c.s, c.i, c.j
|
||||
if i+1 > j then
|
||||
c:underflow(i+1)
|
||||
s, i, j = c.s, c.i, c.j
|
||||
end
|
||||
c.i = i+2
|
||||
return unpack('>i2', s, i)
|
||||
end
|
||||
|
||||
local function unpack_int32 (c)
|
||||
local s, i, j = c.s, c.i, c.j
|
||||
if i+3 > j then
|
||||
c:underflow(i+3)
|
||||
s, i, j = c.s, c.i, c.j
|
||||
end
|
||||
c.i = i+4
|
||||
return unpack('>i4', s, i)
|
||||
end
|
||||
|
||||
local function unpack_int64 (c)
|
||||
local s, i, j = c.s, c.i, c.j
|
||||
if i+7 > j then
|
||||
c:underflow(i+7)
|
||||
s, i, j = c.s, c.i, c.j
|
||||
end
|
||||
c.i = i+8
|
||||
return unpack('>i8', s, i)
|
||||
end
|
||||
|
||||
function m.build_ext (tag, data)
|
||||
return nil
|
||||
end
|
||||
|
||||
local function unpack_ext (c, n, tag)
|
||||
local s, i, j = c.s, c.i, c.j
|
||||
local e = i+n-1
|
||||
if e > j or n < 0 then
|
||||
c:underflow(e)
|
||||
s, i, j = c.s, c.i, c.j
|
||||
e = i+n-1
|
||||
end
|
||||
c.i = i+n
|
||||
return m.build_ext(tag, s:sub(i, e))
|
||||
end
|
||||
|
||||
unpackers = setmetatable({
|
||||
[0xC0] = function () return nil end,
|
||||
[0xC2] = function () return false end,
|
||||
[0xC3] = function () return true end,
|
||||
[0xC4] = function (c) return unpack_str(c, unpack_uint8(c)) end, -- bin8
|
||||
[0xC5] = function (c) return unpack_str(c, unpack_uint16(c)) end, -- bin16
|
||||
[0xC6] = function (c) return unpack_str(c, unpack_uint32(c)) end, -- bin32
|
||||
[0xC7] = function (c) return unpack_ext(c, unpack_uint8(c), unpack_int8(c)) end,
|
||||
[0xC8] = function (c) return unpack_ext(c, unpack_uint16(c), unpack_int8(c)) end,
|
||||
[0xC9] = function (c) return unpack_ext(c, unpack_uint32(c), unpack_int8(c)) end,
|
||||
[0xCA] = unpack_float,
|
||||
[0xCB] = unpack_double,
|
||||
[0xCC] = unpack_uint8,
|
||||
[0xCD] = unpack_uint16,
|
||||
[0xCE] = unpack_uint32,
|
||||
[0xCF] = unpack_uint64,
|
||||
[0xD0] = unpack_int8,
|
||||
[0xD1] = unpack_int16,
|
||||
[0xD2] = unpack_int32,
|
||||
[0xD3] = unpack_int64,
|
||||
[0xD4] = function (c) return unpack_ext(c, 1, unpack_int8(c)) end,
|
||||
[0xD5] = function (c) return unpack_ext(c, 2, unpack_int8(c)) end,
|
||||
[0xD6] = function (c) return unpack_ext(c, 4, unpack_int8(c)) end,
|
||||
[0xD7] = function (c) return unpack_ext(c, 8, unpack_int8(c)) end,
|
||||
[0xD8] = function (c) return unpack_ext(c, 16, unpack_int8(c)) end,
|
||||
[0xD9] = function (c) return unpack_str(c, unpack_uint8(c)) end,
|
||||
[0xDA] = function (c) return unpack_str(c, unpack_uint16(c)) end,
|
||||
[0xDB] = function (c) return unpack_str(c, unpack_uint32(c)) end,
|
||||
[0xDC] = function (c) return unpack_array(c, unpack_uint16(c)) end,
|
||||
[0xDD] = function (c) return unpack_array(c, unpack_uint32(c)) end,
|
||||
[0xDE] = function (c) return unpack_map(c, unpack_uint16(c)) end,
|
||||
[0xDF] = function (c) return unpack_map(c, unpack_uint32(c)) end,
|
||||
}, {
|
||||
__index = function (t, k)
|
||||
if k < 0xC0 then
|
||||
if k < 0x80 then
|
||||
return function (c, val) return val end
|
||||
elseif k < 0x90 then
|
||||
return function (c, val) return unpack_map(c, val & 0xF) end
|
||||
elseif k < 0xA0 then
|
||||
return function (c, val) return unpack_array(c, val & 0xF) end
|
||||
else
|
||||
return function (c, val) return unpack_str(c, val & 0x1F) end
|
||||
end
|
||||
elseif k > 0xDF then
|
||||
return function (c, val) return val - 0x100 end
|
||||
else
|
||||
return function () error("unpack '" .. format('%#x', k) .. "' is unimplemented") end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
local function cursor_string (str)
|
||||
return {
|
||||
s = str,
|
||||
i = 1,
|
||||
j = #str,
|
||||
underflow = function ()
|
||||
error "missing bytes"
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local function cursor_loader (ld)
|
||||
return {
|
||||
s = '',
|
||||
i = 1,
|
||||
j = 0,
|
||||
underflow = function (self, e)
|
||||
self.s = self.s:sub(self.i)
|
||||
e = e - self.i + 1
|
||||
self.i = 1
|
||||
self.j = 0
|
||||
while e > self.j do
|
||||
local chunk = ld()
|
||||
if not chunk then
|
||||
error "missing bytes"
|
||||
end
|
||||
self.s = self.s .. chunk
|
||||
self.j = #self.s
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
function m.unpack (s)
|
||||
checktype('unpack', 1, s, 'string')
|
||||
local cursor = cursor_string(s)
|
||||
local data = unpack_cursor(cursor)
|
||||
if cursor.i <= cursor.j then
|
||||
error "extra bytes"
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
function m.unpacker (src)
|
||||
if type(src) == 'string' then
|
||||
local cursor = cursor_string(src)
|
||||
return function ()
|
||||
if cursor.i <= cursor.j then
|
||||
return cursor.i, unpack_cursor(cursor)
|
||||
end
|
||||
end
|
||||
elseif type(src) == 'function' then
|
||||
local cursor = cursor_loader(src)
|
||||
return function ()
|
||||
if cursor.i > cursor.j then
|
||||
pcall(cursor.underflow, cursor, cursor.i)
|
||||
end
|
||||
if cursor.i <= cursor.j then
|
||||
return true, unpack_cursor(cursor)
|
||||
end
|
||||
end
|
||||
else
|
||||
argerror('unpacker', 1, "string or function expected, got " .. type(src))
|
||||
end
|
||||
end
|
||||
|
||||
set_string'string_compat'
|
||||
set_integer'unsigned'
|
||||
if #pack('n', 0.0) == 4 then
|
||||
m.small_lua = true
|
||||
unpackers[0xCB] = nil -- double
|
||||
unpackers[0xCF] = nil -- uint64
|
||||
unpackers[0xD3] = nil -- int64
|
||||
set_number'float'
|
||||
else
|
||||
m.full64bits = true
|
||||
set_number'double'
|
||||
if #pack('n', 0.0) > 8 then
|
||||
m.long_double = true
|
||||
end
|
||||
end
|
||||
set_array'without_hole'
|
||||
|
||||
m._VERSION = '0.5.2'
|
||||
m._DESCRIPTION = "lua-MessagePack : a pure Lua 5.3 implementation"
|
||||
m._COPYRIGHT = "Copyright (c) 2012-2019 Francois Perrad"
|
||||
return m
|
40
etc/events.lua
Normal file
40
etc/events.lua
Normal file
|
@ -0,0 +1,40 @@
|
|||
--[[ Events library
|
||||
-- (c) Er2 2021 <er2@dismail.de>
|
||||
-- Zlib License
|
||||
--]]
|
||||
|
||||
local events = {}
|
||||
events.__index = events
|
||||
|
||||
function events:_add(t, n, f)
|
||||
table.insert(self._ev_, {
|
||||
type = t,
|
||||
name = n,
|
||||
fn = f,
|
||||
})
|
||||
end
|
||||
|
||||
function events:on(n,f) self:_add('on', n,f) end
|
||||
function events:once(n,f) self:_add('once', n,f) end
|
||||
|
||||
function events:_ev(t, i, name, ...)
|
||||
local v = t[i]
|
||||
if v.name == name then
|
||||
v.fn(...)
|
||||
if v.type == 'once' then table.remove(t, i) end
|
||||
end
|
||||
end
|
||||
|
||||
function events:emit(name, ...)
|
||||
local t = self._ev_
|
||||
for i = 1, #t do
|
||||
local v = t[i] or {}
|
||||
if type(v) == 'table'
|
||||
and type(v.name) == 'string'
|
||||
and type(v.type) == 'string'
|
||||
and type(v.fn) == 'function'
|
||||
then self:_ev(t, i, name, ...) end
|
||||
end
|
||||
end
|
||||
|
||||
return function(t) return setmetatable(t, events) end
|
357
etc/json.lua
Normal file
357
etc/json.lua
Normal file
|
@ -0,0 +1,357 @@
|
|||
-- JSON Library
|
||||
--- (c) 2020 rxi
|
||||
--- MIT License
|
||||
|
||||
local json = { _version = "0.1.2" }
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\",
|
||||
[ "\"" ] = "\"",
|
||||
[ "\b" ] = "b",
|
||||
[ "\f" ] = "f",
|
||||
[ "\n" ] = "n",
|
||||
[ "\r" ] = "r",
|
||||
[ "\t" ] = "t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val)
|
||||
return ( encode(val) )
|
||||
end
|
||||
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = nil,
|
||||
}
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
local line_count = 1
|
||||
local col_count = 1
|
||||
for i = 1, idx - 1 do
|
||||
col_count = col_count + 1
|
||||
if str:sub(i, i) == "\n" then
|
||||
line_count = line_count + 1
|
||||
col_count = 1
|
||||
end
|
||||
end
|
||||
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(1, 4), 16 )
|
||||
local n2 = tonumber( s:sub(7, 10), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local res = ""
|
||||
local j = i + 1
|
||||
local k = j
|
||||
|
||||
while j <= #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
|
||||
elseif x == 92 then -- `\`: Escape
|
||||
res = res .. str:sub(k, j - 1)
|
||||
j = j + 1
|
||||
local c = str:sub(j, j)
|
||||
if c == "u" then
|
||||
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
|
||||
or str:match("^%x%x%x%x", j + 1)
|
||||
or decode_error(str, j - 1, "invalid unicode escape in string")
|
||||
res = res .. parse_unicode_escape(hex)
|
||||
j = j + #hex
|
||||
else
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
res = res .. escape_char_map_inv[c]
|
||||
end
|
||||
k = j + 1
|
||||
|
||||
elseif x == 34 then -- `"`: End of string
|
||||
res = res .. str:sub(k, j - 1)
|
||||
return res, j + 1
|
||||
end
|
||||
|
||||
j = j + 1
|
||||
end
|
||||
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = tonumber(s)
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
||||
idx = next_char(str, idx, space_chars, true)
|
||||
if idx <= #str then
|
||||
decode_error(str, idx, "trailing garbage")
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
return json
|
Loading…
Add table
Add a link
Reference in a new issue