148 lines
3.7 KiB
Lua

--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <harningt@gmail.com>
]]
local lpeg = require("lpeg")
local error = error
local object = require("json.decode.object")
local array = require("json.decode.array")
local merge = require("json.util").merge
local util = require("json.decode.util")
local setmetatable, getmetatable = setmetatable, getmetatable
local assert = assert
local ipairs, pairs = ipairs, pairs
local string_char = require("string").char
local require = require
module("json.decode")
local modulesToLoad = {
"array",
"object",
"strings",
"number",
"calls",
"others"
}
local loadedModules = {
}
default = {
unicodeWhitespace = true,
initialObject = false
}
local modes_defined = { "default", "strict", "simple" }
simple = {}
strict = {
unicodeWhitespace = true,
initialObject = true
}
-- Register generic value type
util.register_type("VALUE")
for _,name in ipairs(modulesToLoad) do
local mod = require("json.decode." .. name)
for _, mode in pairs(modes_defined) do
if mod[mode] then
_M[mode][name] = mod[mode]
end
end
loadedModules[name] = mod
-- Register types
if mod.register_types then
mod.register_types()
end
end
-- Shift over default into defaultOptions to permit build optimization
local defaultOptions = default
default = nil
local function buildDecoder(mode)
mode = mode and merge({}, defaultOptions, mode) or defaultOptions
local ignored = mode.unicodeWhitespace and util.unicode_ignored or util.ascii_ignored
-- Store 'ignored' in the global options table
mode.ignored = ignored
local value_id = util.types.VALUE
local value_type = lpeg.V(value_id)
local object_type = lpeg.V(util.types.OBJECT)
local array_type = lpeg.V(util.types.ARRAY)
local grammar = {
[1] = mode.initialObject and (ignored * (object_type + array_type)) or value_type
}
-- Additional state storage for modules
local state = {}
for _, name in pairs(modulesToLoad) do
local mod = loadedModules[name]
mod.load_types(mode[name], mode, grammar, state)
end
-- HOOK VALUE TYPE WITH WHITESPACE
grammar[value_id] = ignored * grammar[value_id] * ignored
local compiled_grammar = lpeg.P(grammar) * ignored
-- If match-time-capture is supported, implement Cmt workaround for deep captures
if lpeg.Cmt then
if mode.initialObject then
-- Patch the grammar and recompile for VALUE usage
grammar[1] = value_type
state.VALUE_MATCH = lpeg.P(grammar) * ignored
else
state.VALUE_MATCH = compiled_grammar
end
end
-- Only add terminator & pos capture for final grammar since it is expected that there is extra data
-- when using VALUE_MATCH internally
compiled_grammar = compiled_grammar * lpeg.Cp() * -1
return function(data)
local ret, next_index = lpeg.match(compiled_grammar, data)
assert(nil ~= next_index, "Invalid JSON data")
return ret
end
end
-- Since 'default' is nil, we cannot take map it
local defaultDecoder = buildDecoder(default)
local prebuilt_decoders = {}
for _, mode in pairs(modes_defined) do
if _M[mode] ~= nil then
prebuilt_decoders[_M[mode]] = buildDecoder(_M[mode])
end
end
--[[
Options:
number => number decode options
string => string decode options
array => array decode options
object => object decode options
initialObject => whether or not to require the initial object to be a table/array
allowUndefined => whether or not to allow undefined values
]]
function getDecoder(mode)
mode = mode == true and strict or mode or default
local decoder = mode == nil and defaultDecoder or prebuilt_decoders[mode]
if decoder then
return decoder
end
return buildDecoder(mode)
end
function decode(data, mode)
local decoder = getDecoder(mode)
return decoder(data)
end
local mt = getmetatable(_M) or {}
mt.__call = function(self, ...)
return decode(...)
end
setmetatable(_M, mt)