From 2b29dded6d9fa2d36031244cd6cfedf52d4255f1 Mon Sep 17 00:00:00 2001 From: Thomas Harning Jr Date: Tue, 5 Aug 2008 00:27:46 -0400 Subject: [PATCH] decoder: Refactored decoding mechanism to permit building even more customized decoders * Regression tests now use a slightly altered parser to handle Inf * Each individual parser (number,string,array,object) has a 'strict' and 'default' mode table * Each full parser has a 'strict' and 'default' mode table --- src/json/decode.lua | 113 +++++++++++++++++++++++---------------------- src/json/decode/array.lua | 6 +++ src/json/decode/number.lua | 7 +++ src/json/decode/object.lua | 12 ++++- src/json/decode/util.lua | 6 ++- tests/regressionTest.lua | 11 ++++- 6 files changed, 95 insertions(+), 60 deletions(-) diff --git a/src/json/decode.lua b/src/json/decode.lua index ff64f8a..675999c 100644 --- a/src/json/decode.lua +++ b/src/json/decode.lua @@ -31,9 +31,6 @@ local ignored = util.ignored local VALUE, TABLE, ARRAY = util.VALUE, util.TABLE, util.ARRAY -local captureString = strings.buildCapture(strings.default) -local strictCaptureString = strings.buildCapture(strings.strict) - -- For null and undefined, use the util.null value to preserve null-ness local booleanCapture = lpeg.P("true") * lpeg.Cc(true) @@ -42,64 +39,72 @@ local booleanCapture = local nullCapture = lpeg.P("null") * lpeg.Cc(nullValue) local undefinedCapture = lpeg.P("undefined") * lpeg.Cc(undefinedValue) -local function buildValueCapture(nullValue, undefinedValue, allowUndefined, allowNaN, strictMinusSpace, strictString) - local ret = ( - (strictString and strictCaptureString or captureString) - + number.buildCapture({nan = allowNaN, inf = allowNaN, strict = strictMinusSpace}) +default = { + object = object.default, + array = array.default, + number = number.default, + string = strings.default, + allowUndefined = true +} +strict = { + object = object.strict, + array = array.strict, + number = number.strict, + string = strings.strict, + initialObject = true +} + +local function buildDecoder(mode) + local arrayCapture = array.buildCapture(mode.array) + local objectCapture = object.buildCapture(mode.object) + local numberCapture = number.buildCapture(mode.number) + local stringCapture = strings.buildCapture(mode.string) + local valueCapture = ( + stringCapture + + numberCapture + booleanCapture + nullCapture ) - if allowUndefined then - ret = ret + undefinedCapture + if mode.allowUndefined then + valueCapture = valueCapture + undefinedCapture + end + valueCapture = valueCapture + lpeg.V(TABLE) + lpeg.V(ARRAY) + valueCapture = ignored * valueCapture * ignored + local grammar = lpeg.P({ + [1] = mode.initialObject and (lpeg.V(TABLE) + lpeg.V(ARRAY)) or lpeg.V(VALUE), + [VALUE] = valueCapture, + [TABLE] = objectCapture, + [ARRAY] = arrayCapture + }) * ignored * -1 + return function(data) + util.doInit() + return assert(lpeg.match(grammar, data), "Invalid JSON data") end - -- Allow for a table or array as a value - ret = ret + lpeg.V(TABLE) + lpeg.V(ARRAY) - ret = ignored * ret * ignored - return ret end -local valueCapture = buildValueCapture(nullValue, nullValue, true, true, false, false) - --- Current deviation to permit round-tripping --- Allow inf/nan -local strictValueCapture = buildValueCapture(nullValue, nil, false, true, true, true) - -local strictLimiter = util.buildDepthLimit(20) - -local tableCapture = object.buildCapture() -local strictTableCapture = object.buildCapture({ - number = false, - identifier = false, - trailingComma = false, - depthLimiter = strictLimiter -}) - -local arrayCapture = array.buildCapture() -local strictArrayCapture = array.buildCapture({ - trailingComma = false, - depthLimiter = strictLimiter -}) - -local function er(_, i) error("Invalid JSON data at: " .. tostring(i)) end - --- Deviation: allow for trailing comma, allow for "undefined" to be a value... -local grammar = lpeg.P({ - [1] = lpeg.V(VALUE), - [VALUE] = valueCapture, - [TABLE] = tableCapture, - [ARRAY] = arrayCapture -}) * ignored * (-1 + lpeg.P(er)) - -local strictGrammar = lpeg.P({ - [1] = lpeg.V(TABLE) + lpeg.V(ARRAY), -- Initial value MUST be an object or array - [VALUE] = strictValueCapture, - [TABLE] = strictTableCapture, - [ARRAY] = strictArrayCapture -}) * ignored * (-1 + lpeg.P(er)) +local strictDecoder, defaultDecoder = buildDecoder(strict), buildDecoder(default) +--[[ +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) + if mode == strict and strictDecoder then + return strictDecoder + elseif mode == default and defaultDecoder then + return defaultDecoder + end + return buildDecoder(mode) +end -function decode(data, strict) - util.doInit() - return (assert(lpeg.match(not strict and grammar or strictGrammar, data), "Invalid JSON data")) +function decode(data, strictOrMode) + local mode = strictOrMode == true and strict or strictOrMode or default + local decoder = getDecoder(mode) + return decoder(data) end local mt = getmetatable(_M) or {} diff --git a/src/json/decode/array.lua b/src/json/decode/array.lua index e3e97f1..1e1b406 100644 --- a/src/json/decode/array.lua +++ b/src/json/decode/array.lua @@ -42,6 +42,12 @@ local defaultOptions = { depthLimiter = nil } +default = {} +strict = { + trailingComma = false, + depthLimiter = util.buildDepthLimit(20) +} + function buildCapture(options) options = options and util.merge({}, defaultOptions, options) or defaultOptions local incDepth, decDepth diff --git a/src/json/decode/number.lua b/src/json/decode/number.lua index 743e9a7..db53640 100644 --- a/src/json/decode/number.lua +++ b/src/json/decode/number.lua @@ -31,6 +31,13 @@ local defaultOptions = { frac = true, exp = true } + +default = {} +strict = { + nan = false, + inf = false, + strict = true +} --[[ Options: configuration options for number rules nan: match NaN diff --git a/src/json/decode/object.lua b/src/json/decode/object.lua index 6a99587..6f61a96 100644 --- a/src/json/decode/object.lua +++ b/src/json/decode/object.lua @@ -25,8 +25,16 @@ end local defaultOptions = { number = true, identifier = true, - trailingComma = true, - limitDepth = nil -- 20 + trailingComma = true +} + +default = {} + +strict = { + number = false, + identifier = false, + trailingComma = false, + depthLimiter = util.buildDepthLimit(20) } function buildCapture(options) diff --git a/src/json/decode/util.lua b/src/json/decode/util.lua index ef9406a..d3220ee 100644 --- a/src/json/decode/util.lua +++ b/src/json/decode/util.lua @@ -49,8 +49,11 @@ function doInit() end end +-- Current depth is persistent +-- If more complex depth management needed, a new system would need to be setup +local currentDepth = 0 + function buildDepthLimit(limit) - local currentDepth = 0 local function init() currentDepth = 0 end @@ -66,4 +69,3 @@ function buildDepthLimit(limit) end return {incDepth, decDepth} end - diff --git a/tests/regressionTest.lua b/tests/regressionTest.lua index c2967e5..a01666a 100644 --- a/tests/regressionTest.lua +++ b/tests/regressionTest.lua @@ -83,8 +83,15 @@ end print("Testing lax/fast mode:") TestParser(function(data) return json.decode(data) end, {"test/pass","test/fail_strict"}, {"test/fail_all"},{"test/roundtrip","test/roundtrip_lax"}) -print("Testing strict mode:") -TestParser(function(data) return json.decode(data, true) end, {"test/pass"}, {"test/fail_strict","test/fail_all"}, {"test/roundtrip"}) +print("Testing (mostly) strict mode:") +local strict = json.decode.util.merge({}, json.decode.strict, { + number = { + nan = false, + inf = true, + strict = true + } +}) +TestParser(function(data) return json.decode(data, strict) end, {"test/pass"}, {"test/fail_strict","test/fail_all"}, {"test/roundtrip"}) if not success then os.exit(1) -- 2.11.4.GIT