all: 5.2 compatibility
[luajson.git] / lua / json / decode / calls.lua
blob9aa99d7d552f2247ea6f3f0f3cbace13352813b6
1 --[[
2 Licensed according to the included 'LICENSE' document
3 Author: Thomas Harning Jr <harningt@gmail.com>
4 ]]
5 local lpeg = require("lpeg")
6 local tostring = tostring
7 local pairs, ipairs = pairs, ipairs
8 local next, type = next, type
9 local error = error
11 local util = require("json.decode.util")
13 local buildCall = require("json.util").buildCall
15 local getmetatable = getmetatable
17 local is_52 = _VERSION == "Lua 5.2"
18 local _G = _G
20 if is_52 then
21 _ENV = nil
22 end
24 local defaultOptions = {
25 defs = nil,
26 -- By default, do not allow undefined calls to be de-serialized as call objects
27 allowUndefined = false
30 -- No real default-option handling needed...
31 local default = nil
32 local strict = nil
34 local isPattern
35 if lpeg.type then
36 function isPattern(value)
37 return lpeg.type(value) == 'pattern'
38 end
39 else
40 local metaAdd = getmetatable(lpeg.P("")).__add
41 function isPattern(value)
42 return getmetatable(value).__add == metaAdd
43 end
44 end
46 local function buildDefinedCaptures(argumentCapture, defs)
47 local callCapture
48 if not defs then return end
49 for name, func in pairs(defs) do
50 if type(name) ~= 'string' and not isPattern(name) then
51 error("Invalid functionCalls name: " .. tostring(name) .. " not a string or LPEG pattern")
52 end
53 -- Allow boolean or function to match up w/ encoding permissions
54 if type(func) ~= 'boolean' and type(func) ~= 'function' then
55 error("Invalid functionCalls item: " .. name .. " not a function")
56 end
57 local nameCallCapture
58 if type(name) == 'string' then
59 nameCallCapture = lpeg.P(name .. "(") * lpeg.Cc(name)
60 else
61 -- Name matcher expected to produce a capture
62 nameCallCapture = name * "("
63 end
64 -- Call func over nameCallCapture and value to permit function receiving name
66 -- Process 'func' if it is not a function
67 if type(func) == 'boolean' then
68 local allowed = func
69 func = function(name, ...)
70 if not allowed then
71 error("Function call on '" .. name .. "' not permitted")
72 end
73 return buildCall(name, ...)
74 end
75 else
76 local inner_func = func
77 func = function(...)
78 return (inner_func(...))
79 end
80 end
81 local newCapture = (nameCallCapture * argumentCapture) / func * ")"
82 if not callCapture then
83 callCapture = newCapture
84 else
85 callCapture = callCapture + newCapture
86 end
87 end
88 return callCapture
89 end
91 local function buildCapture(options, global_options, state)
92 if not options -- No ops, don't bother to parse
93 or not (options.defs and (nil ~= next(options.defs)) or options.allowUndefined) then
94 return nil
95 end
96 -- Allow zero or more arguments separated by commas
97 local value = lpeg.V(util.types.VALUE)
98 if lpeg.Cmt then
99 value = lpeg.Cmt(lpeg.Cp(), function(str, i)
100 -- Decode one value then return
101 local END_MARKER = {}
102 local pattern =
103 -- Found empty segment
104 #lpeg.P(')' * lpeg.Cc(END_MARKER) * lpeg.Cp())
105 -- Found a value + captured, check for required , or ) + capture next pos
106 + state.VALUE_MATCH * #(lpeg.P(',') + lpeg.P(')')) * lpeg.Cp()
107 local capture, i = pattern:match(str, i)
108 if END_MARKER == capture then
109 return i
110 elseif (i == nil and capture == nil) then
111 return false
112 else
113 return i, capture
115 end)
117 local argumentCapture = (value * (lpeg.P(",") * value)^0) + 0
118 local callCapture = buildDefinedCaptures(argumentCapture, options.defs)
119 if options.allowUndefined then
120 local function func(name, ...)
121 return buildCall(name, ...)
123 -- Identifier-type-match
124 local nameCallCapture = lpeg.C(util.identifier) * "("
125 local newCapture = (nameCallCapture * argumentCapture) / func * ")"
126 if not callCapture then
127 callCapture = newCapture
128 else
129 callCapture = callCapture + newCapture
132 return callCapture
135 local function load_types(options, global_options, grammar, state)
136 local capture = buildCapture(options, global_options, state)
137 if capture then
138 util.append_grammar_item(grammar, "VALUE", capture)
142 local calls = {
143 default = default,
144 strict = strict,
145 load_types = load_types
148 if not is_52 then
149 _G.json = _G.json or {}
150 _G.json.decode = _G.json.decode
151 _G.json.decode.calls = calls
154 return calls