136 lines
3.8 KiB
Lua

--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local lpeg = require("lpeg")
local tostring = tostring
local pairs, ipairs = pairs, ipairs
local next, type = next, type
local error = error
local util = require("json.decode.util")
local buildCall = require("json.util").buildCall
local getmetatable = getmetatable
module("json.decode.calls")
local defaultOptions = {
defs = nil,
-- By default, do not allow undefined calls to be de-serialized as call objects
allowUndefined = false
}
-- No real default-option handling needed...
default = nil
strict = nil
local isPattern
if lpeg.type then
function isPattern(value)
return lpeg.type(value) == 'pattern'
end
else
local metaAdd = getmetatable(lpeg.P("")).__add
function isPattern(value)
return getmetatable(value).__add == metaAdd
end
end
local function buildDefinedCaptures(argumentCapture, defs)
local callCapture
if not defs then return end
for name, func in pairs(defs) do
if type(name) ~= 'string' and not isPattern(name) then
error("Invalid functionCalls name: " .. tostring(name) .. " not a string or LPEG pattern")
end
-- Allow boolean or function to match up w/ encoding permissions
if type(func) ~= 'boolean' and type(func) ~= 'function' then
error("Invalid functionCalls item: " .. name .. " not a function")
end
local nameCallCapture
if type(name) == 'string' then
nameCallCapture = lpeg.P(name .. "(") * lpeg.Cc(name)
else
-- Name matcher expected to produce a capture
nameCallCapture = name * "("
end
-- Call func over nameCallCapture and value to permit function receiving name
-- Process 'func' if it is not a function
if type(func) == 'boolean' then
local allowed = func
func = function(name, ...)
if not allowed then
error("Function call on '" .. name .. "' not permitted")
end
return buildCall(name, ...)
end
else
local inner_func = func
func = function(...)
return (inner_func(...))
end
end
local newCapture = (nameCallCapture * argumentCapture) / func * ")"
if not callCapture then
callCapture = newCapture
else
callCapture = callCapture + newCapture
end
end
return callCapture
end
local function buildCapture(options, global_options, state)
if not options -- No ops, don't bother to parse
or not (options.defs and (nil ~= next(options.defs)) or options.allowUndefined) then
return nil
end
-- Allow zero or more arguments separated by commas
local value = lpeg.V(util.types.VALUE)
if lpeg.Cmt then
value = lpeg.Cmt(lpeg.Cp(), function(str, i)
-- Decode one value then return
local END_MARKER = {}
local pattern =
-- Found empty segment
#lpeg.P(')' * lpeg.Cc(END_MARKER) * lpeg.Cp())
-- Found a value + captured, check for required , or ) + capture next pos
+ state.VALUE_MATCH * #(lpeg.P(',') + lpeg.P(')')) * lpeg.Cp()
local capture, i = pattern:match(str, i)
if END_MARKER == capture then
return i
elseif (i == nil and capture == nil) then
return false
else
return i, capture
end
end)
end
local argumentCapture = (value * (lpeg.P(",") * value)^0) + 0
local callCapture = buildDefinedCaptures(argumentCapture, options.defs)
if options.allowUndefined then
local function func(name, ...)
return buildCall(name, ...)
end
-- Identifier-type-match
local nameCallCapture = lpeg.C(util.identifier) * "("
local newCapture = (nameCallCapture * argumentCapture) / func * ")"
if not callCapture then
callCapture = newCapture
else
callCapture = callCapture + newCapture
end
end
return callCapture
end
function load_types(options, global_options, grammar, state)
local capture = buildCapture(options, global_options, state)
if capture then
util.append_grammar_item(grammar, "VALUE", capture)
end
end