mirror of
https://github.com/fusionpbx/fusionpbx.git
synced 2026-02-15 23:55:00 +00:00
* add built in ability for microsoft bing speech to text * move json.lua to lower case and more error checking in record_message * Replaced Creative Commons json.lua with lunajson.lua which is MIT license https://github.com/grafi-tt/lunajson/blob/master/LICENSE
526 lines
12 KiB
Lua
526 lines
12 KiB
Lua
local error = error
|
|
local byte, char, find, gsub, match, sub = string.byte, string.char, string.find, string.gsub, string.match, string.sub
|
|
local tonumber = tonumber
|
|
local tostring, type, unpack = tostring, type, table.unpack or unpack
|
|
|
|
-- The function that interprets JSON strings is separated into another file so as to
|
|
-- use bitwise operation to speedup unicode codepoints processing on Lua 5.3.
|
|
local genstrlib
|
|
if _VERSION == "Lua 5.3" then
|
|
genstrlib = require 'resources.functions.lunajson._str_lib_lua53'
|
|
else
|
|
genstrlib = require 'resources.functions.lunajson._str_lib'
|
|
end
|
|
|
|
local _ENV = nil
|
|
|
|
local function nop() end
|
|
|
|
local function newparser(src, saxtbl)
|
|
local json, jsonnxt
|
|
local jsonlen, pos, acc = 0, 1, 0
|
|
|
|
-- `f` is the temporary for dispatcher[c] and
|
|
-- the dummy for the first return value of `find`
|
|
local dispatcher, f
|
|
|
|
-- initialize
|
|
if type(src) == 'string' then
|
|
json = src
|
|
jsonlen = #json
|
|
jsonnxt = function()
|
|
json = ''
|
|
jsonlen = 0
|
|
jsonnxt = nop
|
|
end
|
|
else
|
|
jsonnxt = function()
|
|
acc = acc + jsonlen
|
|
pos = 1
|
|
repeat
|
|
json = src()
|
|
if not json then
|
|
json = ''
|
|
jsonlen = 0
|
|
jsonnxt = nop
|
|
return
|
|
end
|
|
jsonlen = #json
|
|
until jsonlen > 0
|
|
end
|
|
jsonnxt()
|
|
end
|
|
|
|
local sax_startobject = saxtbl.startobject or nop
|
|
local sax_key = saxtbl.key or nop
|
|
local sax_endobject = saxtbl.endobject or nop
|
|
local sax_startarray = saxtbl.startarray or nop
|
|
local sax_endarray = saxtbl.endarray or nop
|
|
local sax_string = saxtbl.string or nop
|
|
local sax_number = saxtbl.number or nop
|
|
local sax_boolean = saxtbl.boolean or nop
|
|
local sax_null = saxtbl.null or nop
|
|
|
|
--[[
|
|
Helper
|
|
--]]
|
|
local function tryc()
|
|
local c = byte(json, pos)
|
|
if not c then
|
|
jsonnxt()
|
|
c = byte(json, pos)
|
|
end
|
|
return c
|
|
end
|
|
|
|
local function parseerror(errmsg)
|
|
error("parse error at " .. acc + pos .. ": " .. errmsg)
|
|
end
|
|
|
|
local function tellc()
|
|
return tryc() or parseerror("unexpected termination")
|
|
end
|
|
|
|
local function spaces() -- skip spaces and prepare the next char
|
|
while true do
|
|
f, pos = find(json, '^[ \n\r\t]*', pos)
|
|
if pos ~= jsonlen then
|
|
pos = pos+1
|
|
return
|
|
end
|
|
if jsonlen == 0 then
|
|
parseerror("unexpected termination")
|
|
end
|
|
jsonnxt()
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Invalid
|
|
--]]
|
|
local function f_err()
|
|
parseerror('invalid value')
|
|
end
|
|
|
|
--[[
|
|
Constants
|
|
--]]
|
|
-- fallback slow constants parser
|
|
local function generic_constant(target, targetlen, ret, sax_f)
|
|
for i = 1, targetlen do
|
|
local c = tellc()
|
|
if byte(target, i) ~= c then
|
|
parseerror("invalid char")
|
|
end
|
|
pos = pos+1
|
|
end
|
|
return sax_f(ret)
|
|
end
|
|
|
|
-- null
|
|
local function f_nul()
|
|
if sub(json, pos, pos+2) == 'ull' then
|
|
pos = pos+3
|
|
return sax_null(nil)
|
|
end
|
|
return generic_constant('ull', 3, nil, sax_null)
|
|
end
|
|
|
|
-- false
|
|
local function f_fls()
|
|
if sub(json, pos, pos+3) == 'alse' then
|
|
pos = pos+4
|
|
return sax_boolean(false)
|
|
end
|
|
return generic_constant('alse', 4, false, sax_boolean)
|
|
end
|
|
|
|
-- true
|
|
local function f_tru()
|
|
if sub(json, pos, pos+2) == 'rue' then
|
|
pos = pos+3
|
|
return sax_boolean(true)
|
|
end
|
|
return generic_constant('rue', 3, true, sax_boolean)
|
|
end
|
|
|
|
--[[
|
|
Numbers
|
|
Conceptually, the longest prefix that matches to `(0|[1-9][0-9]*)(\.[0-9]*)?([eE][+-]?[0-9]*)?`
|
|
(in regexp) is captured as a number and its conformance to the JSON spec is checked.
|
|
--]]
|
|
-- deal with non-standard locales
|
|
local radixmark = match(tostring(0.5), '[^0-9]')
|
|
local fixedtonumber = tonumber
|
|
if radixmark ~= '.' then -- deals with non-standard locales
|
|
if find(radixmark, '%W') then
|
|
radixmark = '%' .. radixmark
|
|
end
|
|
fixedtonumber = function(s)
|
|
return tonumber(gsub(s, '.', radixmark))
|
|
end
|
|
end
|
|
|
|
-- fallback slow parser
|
|
local function generic_number(mns)
|
|
local buf = {}
|
|
local i = 1
|
|
|
|
local c = byte(json, pos)
|
|
pos = pos+1
|
|
|
|
local function nxt()
|
|
buf[i] = c
|
|
i = i+1
|
|
c = tryc()
|
|
pos = pos+1
|
|
end
|
|
|
|
if c == 0x30 then
|
|
nxt()
|
|
else
|
|
repeat nxt() until not (c and 0x30 <= c and c < 0x3A)
|
|
end
|
|
if c == 0x2E then
|
|
nxt()
|
|
if not (c and 0x30 <= c and c < 0x3A) then
|
|
parseerror('invalid number')
|
|
end
|
|
repeat nxt() until not (c and 0x30 <= c and c < 0x3A)
|
|
end
|
|
if c == 0x45 or c == 0x65 then
|
|
nxt()
|
|
if c == 0x2B or c == 0x2D then
|
|
nxt()
|
|
end
|
|
if not (c and 0x30 <= c and c < 0x3A) then
|
|
parseerror('invalid number')
|
|
end
|
|
repeat nxt() until not (c and 0x30 <= c and c < 0x3A)
|
|
end
|
|
pos = pos-1
|
|
|
|
local num = char(unpack(buf))
|
|
num = fixedtonumber(num)-0.0
|
|
if mns then
|
|
num = -num
|
|
end
|
|
return sax_number(num)
|
|
end
|
|
|
|
-- `0(\.[0-9]*)?([eE][+-]?[0-9]*)?`
|
|
local function f_zro(mns)
|
|
local postmp = pos
|
|
local num
|
|
local c = byte(json, postmp)
|
|
|
|
if c == 0x2E then -- is this `.`?
|
|
num = match(json, '^.[0-9]*', pos) -- skipping 0
|
|
local numlen = #num
|
|
if numlen == 1 then
|
|
pos = pos-1
|
|
return generic_number(mns)
|
|
end
|
|
postmp = pos + numlen
|
|
c = byte(json, postmp)
|
|
end
|
|
|
|
if c == 0x45 or c == 0x65 then -- is this e or E?
|
|
local numexp = match(json, '^[^eE]*[eE][-+]?[0-9]+', pos)
|
|
if not numexp then
|
|
pos = pos-1
|
|
return generic_number(mns)
|
|
end
|
|
if num then -- since `0e.*` is always 0.0, ignore those
|
|
num = numexp
|
|
end
|
|
postmp = pos + #numexp
|
|
end
|
|
|
|
if postmp > jsonlen then
|
|
pos = pos-1
|
|
return generic_number(mns)
|
|
end
|
|
pos = postmp
|
|
if num then
|
|
num = fixedtonumber(num)
|
|
else
|
|
num = 0.0
|
|
end
|
|
if mns then
|
|
num = -num
|
|
end
|
|
return sax_number(num)
|
|
end
|
|
|
|
-- `[1-9][0-9]*(\.[0-9]*)?([eE][+-]?[0-9]*)?`
|
|
local function f_num(mns)
|
|
pos = pos-1
|
|
local num = match(json, '^.[0-9]*%.?[0-9]*', pos)
|
|
if byte(num, -1) == 0x2E then
|
|
return generic_number(mns)
|
|
end
|
|
local postmp = pos + #num
|
|
local c = byte(json, postmp)
|
|
|
|
if c == 0x45 or c == 0x65 then -- e or E?
|
|
num = match(json, '^[^eE]*[eE][-+]?[0-9]+', pos)
|
|
if not num then
|
|
return generic_number(mns)
|
|
end
|
|
postmp = pos + #num
|
|
end
|
|
|
|
if postmp > jsonlen then
|
|
return generic_number(mns)
|
|
end
|
|
pos = postmp
|
|
num = fixedtonumber(num)-0.0
|
|
if mns then
|
|
num = -num
|
|
end
|
|
return sax_number(num)
|
|
end
|
|
|
|
-- skip minus sign
|
|
local function f_mns()
|
|
local c = byte(json, pos) or tellc()
|
|
if c then
|
|
pos = pos+1
|
|
if c > 0x30 then
|
|
if c < 0x3A then
|
|
return f_num(true)
|
|
end
|
|
else
|
|
if c > 0x2F then
|
|
return f_zro(true)
|
|
end
|
|
end
|
|
end
|
|
parseerror("invalid number")
|
|
end
|
|
|
|
--[[
|
|
Strings
|
|
--]]
|
|
local f_str_lib = genstrlib(parseerror)
|
|
local f_str_surrogateok = f_str_lib.surrogateok -- whether codepoints for surrogate pair are correctly paired
|
|
local f_str_subst = f_str_lib.subst -- the function passed to gsub that interprets escapes
|
|
|
|
local function f_str(iskey)
|
|
local pos2 = pos
|
|
local newpos
|
|
local str = ''
|
|
local bs
|
|
while true do
|
|
while true do -- search '\' or '"'
|
|
newpos = find(json, '[\\"]', pos2)
|
|
if newpos then
|
|
break
|
|
end
|
|
str = str .. sub(json, pos, jsonlen)
|
|
if pos2 == jsonlen+2 then
|
|
pos2 = 2
|
|
else
|
|
pos2 = 1
|
|
end
|
|
jsonnxt()
|
|
end
|
|
if byte(json, newpos) == 0x22 then -- break if '"'
|
|
break
|
|
end
|
|
pos2 = newpos+2 -- skip '\<char>'
|
|
bs = true -- remember that backslash occurs
|
|
end
|
|
str = str .. sub(json, pos, newpos-1)
|
|
pos = newpos+1
|
|
|
|
if bs then -- check if backslash occurs
|
|
str = gsub(str, '\\(.)([^\\]*)', f_str_subst) -- interpret escapes
|
|
if not f_str_surrogateok() then
|
|
parseerror("invalid surrogate pair")
|
|
end
|
|
end
|
|
|
|
if iskey then
|
|
return sax_key(str)
|
|
end
|
|
return sax_string(str)
|
|
end
|
|
|
|
--[[
|
|
Arrays, Objects
|
|
--]]
|
|
-- arrays
|
|
local function f_ary()
|
|
sax_startarray()
|
|
spaces()
|
|
if byte(json, pos) ~= 0x5D then -- check the closing bracket ']', that consists an empty array
|
|
local newpos
|
|
while true do
|
|
f = dispatcher[byte(json, pos)] -- parse value
|
|
pos = pos+1
|
|
f()
|
|
f, newpos = find(json, '^[ \n\r\t]*,[ \n\r\t]*', pos) -- check comma
|
|
if not newpos then
|
|
f, newpos = find(json, '^[ \n\r\t]*%]', pos) -- check closing bracket
|
|
if newpos then
|
|
pos = newpos
|
|
break
|
|
end
|
|
spaces() -- since the current chunk can be ended, skip spaces toward following chunks
|
|
local c = byte(json, pos)
|
|
if c == 0x2C then -- check comma again
|
|
pos = pos+1
|
|
spaces()
|
|
newpos = pos-1
|
|
elseif c == 0x5D then -- check closing bracket again
|
|
break
|
|
else
|
|
parseerror("no closing bracket of an array")
|
|
end
|
|
end
|
|
pos = newpos+1
|
|
if pos > jsonlen then
|
|
spaces()
|
|
end
|
|
end
|
|
end
|
|
pos = pos+1
|
|
return sax_endarray()
|
|
end
|
|
|
|
-- objects
|
|
local function f_obj()
|
|
sax_startobject()
|
|
spaces()
|
|
if byte(json, pos) ~= 0x7D then -- check the closing bracket `}`, that consists an empty object
|
|
local newpos
|
|
while true do
|
|
if byte(json, pos) ~= 0x22 then
|
|
parseerror("not key")
|
|
end
|
|
pos = pos+1
|
|
f_str(true)
|
|
f, newpos = find(json, '^[ \n\r\t]*:[ \n\r\t]*', pos) -- check colon
|
|
if not newpos then
|
|
spaces() -- since the current chunk can be ended, skip spaces toward following chunks
|
|
if byte(json, pos) ~= 0x3A then -- check colon again
|
|
parseerror("no colon after a key")
|
|
end
|
|
pos = pos+1
|
|
spaces()
|
|
newpos = pos-1
|
|
end
|
|
pos = newpos+1
|
|
if pos > jsonlen then
|
|
spaces()
|
|
end
|
|
f = dispatcher[byte(json, pos)] -- parse value
|
|
pos = pos+1
|
|
f()
|
|
f, newpos = find(json, '^[ \n\r\t]*,[ \n\r\t]*', pos) -- check comma
|
|
if not newpos then
|
|
f, newpos = find(json, '^[ \n\r\t]*}', pos) -- check closing bracket
|
|
if newpos then
|
|
pos = newpos
|
|
break
|
|
end
|
|
spaces() -- since the current chunk can be ended, skip spaces toward following chunks
|
|
local c = byte(json, pos)
|
|
if c == 0x2C then -- check comma again
|
|
pos = pos+1
|
|
spaces()
|
|
newpos = pos-1
|
|
elseif c == 0x7D then -- check closing bracket again
|
|
break
|
|
else
|
|
parseerror("no closing bracket of an object")
|
|
end
|
|
end
|
|
pos = newpos+1
|
|
if pos > jsonlen then
|
|
spaces()
|
|
end
|
|
end
|
|
end
|
|
pos = pos+1
|
|
return sax_endobject()
|
|
end
|
|
|
|
--[[
|
|
The jump table to dispatch a parser for a value, indexed by the code of the value's first char.
|
|
Key should be non-nil.
|
|
--]]
|
|
dispatcher = {
|
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
|
f_err, f_err, f_str, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_mns, f_err, f_err,
|
|
f_zro, f_num, f_num, f_num, f_num, f_num, f_num, f_num, f_num, f_num, f_err, f_err, f_err, f_err, f_err, f_err,
|
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
|
|
f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_ary, f_err, f_err, f_err, f_err,
|
|
f_err, f_err, f_err, f_err, f_err, f_err, f_fls, f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_nul, f_err,
|
|
f_err, f_err, f_err, f_err, f_tru, f_err, f_err, f_err, f_err, f_err, f_err, f_obj, f_err, f_err, f_err, f_err,
|
|
}
|
|
dispatcher[0] = f_err
|
|
|
|
--[[
|
|
public funcitons
|
|
--]]
|
|
local function run()
|
|
spaces()
|
|
f = dispatcher[byte(json, pos)]
|
|
pos = pos+1
|
|
f()
|
|
end
|
|
|
|
local function read(n)
|
|
if n < 0 then
|
|
error("the argument must be non-negative")
|
|
end
|
|
local pos2 = (pos-1) + n
|
|
local str = sub(json, pos, pos2)
|
|
while pos2 > jsonlen and jsonlen ~= 0 do
|
|
jsonnxt()
|
|
pos2 = pos2 - (jsonlen - (pos-1))
|
|
str = str .. sub(json, pos, pos2)
|
|
end
|
|
if jsonlen ~= 0 then
|
|
pos = pos2+1
|
|
end
|
|
return str
|
|
end
|
|
|
|
local function tellpos()
|
|
return acc + pos
|
|
end
|
|
|
|
return {
|
|
run = run,
|
|
tryc = tryc,
|
|
read = read,
|
|
tellpos = tellpos,
|
|
}
|
|
end
|
|
|
|
local function newfileparser(fn, saxtbl)
|
|
local fp = io.open(fn)
|
|
local function gen()
|
|
local s
|
|
if fp then
|
|
s = fp:read(8192)
|
|
if not s then
|
|
fp:close()
|
|
fp = nil
|
|
end
|
|
end
|
|
return s
|
|
end
|
|
return newparser(gen, saxtbl)
|
|
end
|
|
|
|
return {
|
|
newparser = newparser,
|
|
newfileparser = newfileparser
|
|
}
|