decode+docs+tests: adds support for (array/object/calls).allowEmptyElement to address...
[luajson.git] / lua / json / decode / composite.lua
blob0dda1dd433ea224c79192bda5bea496ca5b02de7
1 --[[
2 Licensed according to the included 'LICENSE' document
3 Author: Thomas Harning Jr <harningt@gmail.com>
4 ]]
5 local pairs = pairs
6 local type = type
8 local lpeg = require("lpeg")
10 local util = require("json.decode.util")
11 local jsonutil = require("json.util")
13 local rawset = rawset
15 local assert = assert
16 local tostring = tostring
18 local error = error
19 local getmetatable = getmetatable
21 local _ENV = nil
23 local defaultOptions = {
24 array = {
25 allowEmptyElement = false,
26 trailingComma = true
28 object = {
29 allowEmptyElement = false,
30 trailingComma = true,
31 number = true,
32 identifier = true,
33 setObjectKey = rawset
35 calls = {
36 allowEmptyElement = false,
37 defs = nil,
38 -- By default, do not allow undefined calls to be de-serialized as call objects
39 allowUndefined = false,
40 trailingComma = true
44 local modeOptions = {
45 default = nil,
46 strict = {
47 array = {
48 trailingComma = false
50 object = {
51 trailingComma = false,
52 number = false,
53 identifier = false
58 local function BEGIN_ARRAY(state)
59 state:push()
60 state:new_array()
61 end
62 local function END_ARRAY(state)
63 state:end_array()
64 state:pop()
65 end
67 local function BEGIN_OBJECT(state)
68 state:push()
69 state:new_object()
70 end
71 local function END_OBJECT(state)
72 state:end_object()
73 state:pop()
74 end
76 local function END_CALL(state)
77 state:end_call()
78 state:pop()
79 end
81 local function SET_KEY(state)
82 state:set_key()
83 end
85 local function NEXT_VALUE(state)
86 state:put_value()
87 end
89 local function mergeOptions(options, mode)
90 jsonutil.doOptionMerge(options, true, 'array', defaultOptions, mode and modeOptions[mode])
91 jsonutil.doOptionMerge(options, true, 'object', defaultOptions, mode and modeOptions[mode])
92 jsonutil.doOptionMerge(options, true, 'calls', defaultOptions, mode and modeOptions[mode])
93 end
96 local isPattern
97 if lpeg.type then
98 function isPattern(value)
99 return lpeg.type(value) == 'pattern'
101 else
102 local metaAdd = getmetatable(lpeg.P("")).__add
103 function isPattern(value)
104 return getmetatable(value).__add == metaAdd
109 local function generateSingleCallLexer(name, func)
110 if type(name) ~= 'string' and not isPattern(name) then
111 error("Invalid functionCalls name: " .. tostring(name) .. " not a string or LPEG pattern")
113 -- Allow boolean or function to match up w/ encoding permissions
114 if type(func) ~= 'boolean' and type(func) ~= 'function' then
115 error("Invalid functionCalls item: " .. name .. " not a function")
117 local function buildCallCapture(name)
118 return function(state)
119 if func == false then
120 error("Function call on '" .. name .. "' not permitted")
122 state:push()
123 state:new_call(name, func)
126 local nameCallCapture
127 if type(name) == 'string' then
128 nameCallCapture = lpeg.P(name .. "(") * lpeg.Cc(name) / buildCallCapture
129 else
130 -- Name matcher expected to produce a capture
131 nameCallCapture = name * "(" / buildCallCapture
133 -- Call func over nameCallCapture and value to permit function receiving name
134 return nameCallCapture
137 local function generateNamedCallLexers(options)
138 if not options.calls or not options.calls.defs then
139 return
141 local callCapture
142 for name, func in pairs(options.calls.defs) do
143 local newCapture = generateSingleCallLexer(name, func)
144 if not callCapture then
145 callCapture = newCapture
146 else
147 callCapture = callCapture + newCapture
150 return callCapture
153 local function generateCallLexer(options)
154 local lexer
155 local namedCapture = generateNamedCallLexers(options)
156 if options.calls and options.calls.allowUndefined then
157 lexer = generateSingleCallLexer(lpeg.C(util.identifier), true)
159 if namedCapture then
160 lexer = lexer and lexer + namedCapture or namedCapture
162 if lexer then
163 lexer = lexer + lpeg.P(")") * lpeg.Cc(END_CALL)
165 return lexer
168 local function generateLexer(options)
169 local ignored = options.ignored
170 local array_options, object_options = options.array, options.object
171 local lexer =
172 lpeg.P("[") * lpeg.Cc(BEGIN_ARRAY)
173 + lpeg.P("]") * lpeg.Cc(END_ARRAY)
174 + lpeg.P("{") * lpeg.Cc(BEGIN_OBJECT)
175 + lpeg.P("}") * lpeg.Cc(END_OBJECT)
176 + lpeg.P(":") * lpeg.Cc(SET_KEY)
177 + lpeg.P(",") * lpeg.Cc(NEXT_VALUE)
178 if object_options.identifier then
179 -- Add identifier match w/ validation check that it is in key
180 lexer = lexer + lpeg.C(util.identifier) * ignored * lpeg.P(":") * lpeg.Cc(SET_KEY)
182 local callLexers = generateCallLexer(options)
183 if callLexers then
184 lexer = lexer + callLexers
186 return lexer
189 local composite = {
190 mergeOptions = mergeOptions,
191 generateLexer = generateLexer
194 return composite