Commit a01bda03 authored by zeroday's avatar zeroday
Browse files

Merge pull request #294 from nodemcu/json

Json branch merged to master
parents fa7cf878 808a359e
#!/usr/bin/env lua
-- This benchmark script measures wall clock time and should be
-- run on an unloaded system.
--
-- Your Mileage May Vary.
--
-- Mark Pulford <mark@kyne.com.au>
local json_module = os.getenv("JSON_MODULE") or "cjson"
require "socket"
local json = require(json_module)
local util = require "cjson.util"
local function find_func(mod, funcnames)
for _, v in ipairs(funcnames) do
if mod[v] then
return mod[v]
end
end
return nil
end
local json_encode = find_func(json, { "encode", "Encode", "to_string", "stringify", "json" })
local json_decode = find_func(json, { "decode", "Decode", "to_value", "parse" })
local function average(t)
local total = 0
for _, v in ipairs(t) do
total = total + v
end
return total / #t
end
function benchmark(tests, seconds, rep)
local function bench(func, iter)
-- Use socket.gettime() to measure microsecond resolution
-- wall clock time.
local t = socket.gettime()
for i = 1, iter do
func(i)
end
t = socket.gettime() - t
-- Don't trust any results when the run lasted for less than a
-- millisecond - return nil.
if t < 0.001 then
return nil
end
return (iter / t)
end
-- Roughly calculate the number of interations required
-- to obtain a particular time period.
local function calc_iter(func, seconds)
local iter = 1
local rate
-- Warm up the bench function first.
func()
while not rate do
rate = bench(func, iter)
iter = iter * 10
end
return math.ceil(seconds * rate)
end
local test_results = {}
for name, func in pairs(tests) do
-- k(number), v(string)
-- k(string), v(function)
-- k(number), v(function)
if type(func) == "string" then
name = func
func = _G[name]
end
local iter = calc_iter(func, seconds)
local result = {}
for i = 1, rep do
result[i] = bench(func, iter)
end
-- Remove the slowest half (round down) of the result set
table.sort(result)
for i = 1, math.floor(#result / 2) do
table.remove(result, 1)
end
test_results[name] = average(result)
end
return test_results
end
function bench_file(filename)
local data_json = util.file_load(filename)
local data_obj = json_decode(data_json)
local function test_encode()
json_encode(data_obj)
end
local function test_decode()
json_decode(data_json)
end
local tests = {}
if json_encode then tests.encode = test_encode end
if json_decode then tests.decode = test_decode end
return benchmark(tests, 0.1, 5)
end
-- Optionally load any custom configuration required for this module
local success, data = pcall(util.file_load, ("bench-%s.lua"):format(json_module))
if success then
util.run_script(data, _G)
configure(json)
end
for i = 1, #arg do
local results = bench_file(arg[i])
for k, v in pairs(results) do
print(("%s\t%s\t%d"):format(arg[i], k, v))
end
end
-- vi:ai et sw=4 ts=4:
{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Mark up Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": ["GML", "XML"]
},
"GlossSee": "markup"
}
}
}
}
}
{"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [
{"value": "New", "onclick": "CreateNewDoc()"},
{"value": "Open", "onclick": "OpenDoc()"},
{"value": "Close", "onclick": "CloseDoc()"}
]
}
}}
{"widget": {
"debug": "on",
"window": {
"title": "Sample Konfabulator Widget",
"name": "main_window",
"width": 500,
"height": 500
},
"image": {
"src": "Images/Sun.png",
"name": "sun1",
"hOffset": 250,
"vOffset": 250,
"alignment": "center"
},
"text": {
"data": "Click Here",
"size": 36,
"style": "bold",
"name": "text1",
"hOffset": 250,
"vOffset": 100,
"alignment": "center",
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
}
}}
{"web-app": {
"servlet": [
{
"servlet-name": "cofaxCDS",
"servlet-class": "org.cofax.cds.CDSServlet",
"init-param": {
"configGlossary:installationAt": "Philadelphia, PA",
"configGlossary:adminEmail": "ksm@pobox.com",
"configGlossary:poweredBy": "Cofax",
"configGlossary:poweredByIcon": "/images/cofax.gif",
"configGlossary:staticPath": "/content/static",
"templateProcessorClass": "org.cofax.WysiwygTemplate",
"templateLoaderClass": "org.cofax.FilesTemplateLoader",
"templatePath": "templates",
"templateOverridePath": "",
"defaultListTemplate": "listTemplate.htm",
"defaultFileTemplate": "articleTemplate.htm",
"useJSP": false,
"jspListTemplate": "listTemplate.jsp",
"jspFileTemplate": "articleTemplate.jsp",
"cachePackageTagsTrack": 200,
"cachePackageTagsStore": 200,
"cachePackageTagsRefresh": 60,
"cacheTemplatesTrack": 100,
"cacheTemplatesStore": 50,
"cacheTemplatesRefresh": 15,
"cachePagesTrack": 200,
"cachePagesStore": 100,
"cachePagesRefresh": 10,
"cachePagesDirtyRead": 10,
"searchEngineListTemplate": "forSearchEnginesList.htm",
"searchEngineFileTemplate": "forSearchEngines.htm",
"searchEngineRobotsDb": "WEB-INF/robots.db",
"useDataStore": true,
"dataStoreClass": "org.cofax.SqlDataStore",
"redirectionClass": "org.cofax.SqlRedirection",
"dataStoreName": "cofax",
"dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver",
"dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon",
"dataStoreUser": "sa",
"dataStorePassword": "dataStoreTestQuery",
"dataStoreTestQuery": "SET NOCOUNT ON;select test='test';",
"dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log",
"dataStoreInitConns": 10,
"dataStoreMaxConns": 100,
"dataStoreConnUsageLimit": 100,
"dataStoreLogLevel": "debug",
"maxUrlLength": 500}},
{
"servlet-name": "cofaxEmail",
"servlet-class": "org.cofax.cds.EmailServlet",
"init-param": {
"mailHost": "mail1",
"mailHostOverride": "mail2"}},
{
"servlet-name": "cofaxAdmin",
"servlet-class": "org.cofax.cds.AdminServlet"},
{
"servlet-name": "fileServlet",
"servlet-class": "org.cofax.cds.FileServlet"},
{
"servlet-name": "cofaxTools",
"servlet-class": "org.cofax.cms.CofaxToolsServlet",
"init-param": {
"templatePath": "toolstemplates/",
"log": 1,
"logLocation": "/usr/local/tomcat/logs/CofaxTools.log",
"logMaxSize": "",
"dataLog": 1,
"dataLogLocation": "/usr/local/tomcat/logs/dataLog.log",
"dataLogMaxSize": "",
"removePageCache": "/content/admin/remove?cache=pages&id=",
"removeTemplateCache": "/content/admin/remove?cache=templates&id=",
"fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder",
"lookInContext": 1,
"adminGroupID": 4,
"betaServer": true}}],
"servlet-mapping": {
"cofaxCDS": "/",
"cofaxEmail": "/cofaxutil/aemail/*",
"cofaxAdmin": "/admin/*",
"fileServlet": "/static/*",
"cofaxTools": "/tools/*"},
"taglib": {
"taglib-uri": "cofax.tld",
"taglib-location": "/WEB-INF/tlds/cofax.tld"}}}
{"menu": {
"header": "SVG Viewer",
"items": [
{"id": "Open"},
{"id": "OpenNew", "label": "Open New"},
null,
{"id": "ZoomIn", "label": "Zoom In"},
{"id": "ZoomOut", "label": "Zoom Out"},
{"id": "OriginalView", "label": "Original View"},
null,
{"id": "Quality"},
{"id": "Pause"},
{"id": "Mute"},
null,
{"id": "Find", "label": "Find..."},
{"id": "FindAgain", "label": "Find Again"},
{"id": "Copy"},
{"id": "CopyAgain", "label": "Copy Again"},
{"id": "CopySVG", "label": "Copy SVG"},
{"id": "ViewSVG", "label": "View SVG"},
{"id": "ViewSource", "label": "View Source"},
{"id": "SaveAs", "label": "Save As"},
null,
{"id": "Help"},
{"id": "About", "label": "About Adobe CVG Viewer..."}
]
}}
#!/usr/bin/env perl
# Create test comparison data using a different UTF-8 implementation.
# The generated utf8.dat file must have the following MD5 sum:
# cff03b039d850f370a7362f3313e5268
use strict;
# 0xD800 - 0xDFFF are used to encode supplementary codepoints
# 0x10000 - 0x10FFFF are supplementary codepoints
my (@codepoints) = (0 .. 0xD7FF, 0xE000 .. 0x10FFFF);
my $utf8 = pack("U*", @codepoints);
defined($utf8) or die "Unable create UTF-8 string\n";
open(FH, ">:utf8", "utf8.dat")
or die "Unable to open utf8.dat: $!\n";
print FH $utf8
or die "Unable to write utf8.dat\n";
close(FH);
# vi:ai et sw=4 ts=4:
[ 0.110001,
0.12345678910111,
0.412454033640,
2.6651441426902,
2.718281828459,
3.1415926535898,
2.1406926327793 ]
"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#$%&'()*+,-.\/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f"
\ No newline at end of file
{
"Image": {
"Width": 800,
"Height": 600,
"Title": "View from 15th Floor",
"Thumbnail": {
"Url": "http://www.example.com/image/481989943",
"Height": 125,
"Width": "100"
},
"IDs": [116, 943, 234, 38793]
}
}
[
{
"precision": "zip",
"Latitude": 37.7668,
"Longitude": -122.3959,
"Address": "",
"City": "SAN FRANCISCO",
"State": "CA",
"Zip": "94107",
"Country": "US"
},
{
"precision": "zip",
"Latitude": 37.371991,
"Longitude": -122.026020,
"Address": "",
"City": "SUNNYVALE",
"State": "CA",
"Zip": "94085",
"Country": "US"
}
]
#!/usr/bin/env lua
-- Lua CJSON tests
--
-- Mark Pulford <mark@kyne.com.au>
--
-- Note: The output of this script is easier to read with "less -S"
local json = require "cjson"
local json_safe = require "cjson.safe"
local util = require "cjson.util"
local function gen_raw_octets()
local chars = {}
for i = 0, 255 do chars[i + 1] = string.char(i) end
return table.concat(chars)
end
-- Generate every UTF-16 codepoint, including supplementary codes
local function gen_utf16_escaped()
-- Create raw table escapes
local utf16_escaped = {}
local count = 0
local function append_escape(code)
local esc = ('\\u%04X'):format(code)
table.insert(utf16_escaped, esc)
end
table.insert(utf16_escaped, '"')
for i = 0, 0xD7FF do
append_escape(i)
end
-- Skip 0xD800 - 0xDFFF since they are used to encode supplementary
-- codepoints
for i = 0xE000, 0xFFFF do
append_escape(i)
end
-- Append surrogate pair for each supplementary codepoint
for high = 0xD800, 0xDBFF do
for low = 0xDC00, 0xDFFF do
append_escape(high)
append_escape(low)
end
end
table.insert(utf16_escaped, '"')
return table.concat(utf16_escaped)
end
function load_testdata()
local data = {}
-- Data for 8bit raw <-> escaped octets tests
data.octets_raw = gen_raw_octets()
data.octets_escaped = util.file_load("octets-escaped.dat")
-- Data for \uXXXX -> UTF-8 test
data.utf16_escaped = gen_utf16_escaped()
-- Load matching data for utf16_escaped
local utf8_loaded
utf8_loaded, data.utf8_raw = pcall(util.file_load, "utf8.dat")
if not utf8_loaded then
data.utf8_raw = "Failed to load utf8.dat - please run genutf8.pl"
end
data.table_cycle = {}
data.table_cycle[1] = data.table_cycle
local big = {}
for i = 1, 1100 do
big = { { 10, false, true, json.null }, "string", a = big }
end
data.deeply_nested_data = big
return data
end
function test_decode_cycle(filename)
local obj1 = json.decode(util.file_load(filename))
local obj2 = json.decode(json.encode(obj1))
return util.compare_values(obj1, obj2)
end
-- Set up data used in tests
local Inf = math.huge;
local NaN = math.huge * 0;
local testdata = load_testdata()
local cjson_tests = {
-- Test API variables
{ "Check module name, version",
function () return json._NAME, json._VERSION end, { },
true, { "cjson", "2.1devel" } },
-- Test decoding simple types
{ "Decode string",
json.decode, { '"test string"' }, true, { "test string" } },
{ "Decode numbers",
json.decode, { '[ 0.0, -5e3, -1, 0.3e-3, 1023.2, 0e10 ]' },
true, { { 0.0, -5000, -1, 0.0003, 1023.2, 0 } } },
{ "Decode null",
json.decode, { 'null' }, true, { json.null } },
{ "Decode true",
json.decode, { 'true' }, true, { true } },
{ "Decode false",
json.decode, { 'false' }, true, { false } },
{ "Decode object with numeric keys",
json.decode, { '{ "1": "one", "3": "three" }' },
true, { { ["1"] = "one", ["3"] = "three" } } },
{ "Decode object with string keys",
json.decode, { '{ "a": "a", "b": "b" }' },
true, { { a = "a", b = "b" } } },
{ "Decode array",
json.decode, { '[ "one", null, "three" ]' },
true, { { "one", json.null, "three" } } },
-- Test decoding errors
{ "Decode UTF-16BE [throw error]",
json.decode, { '\0"\0"' },
false, { "JSON parser does not support UTF-16 or UTF-32" } },
{ "Decode UTF-16LE [throw error]",
json.decode, { '"\0"\0' },
false, { "JSON parser does not support UTF-16 or UTF-32" } },
{ "Decode UTF-32BE [throw error]",
json.decode, { '\0\0\0"' },
false, { "JSON parser does not support UTF-16 or UTF-32" } },
{ "Decode UTF-32LE [throw error]",
json.decode, { '"\0\0\0' },
false, { "JSON parser does not support UTF-16 or UTF-32" } },
{ "Decode partial JSON [throw error]",
json.decode, { '{ "unexpected eof": ' },
false, { "Expected value but found T_END at character 21" } },
{ "Decode with extra comma [throw error]",
json.decode, { '{ "extra data": true }, false' },
false, { "Expected the end but found T_COMMA at character 23" } },
{ "Decode invalid escape code [throw error]",
json.decode, { [[ { "bad escape \q code" } ]] },
false, { "Expected object key string but found invalid escape code at character 16" } },
{ "Decode invalid unicode escape [throw error]",
json.decode, { [[ { "bad unicode \u0f6 escape" } ]] },
false, { "Expected object key string but found invalid unicode escape code at character 17" } },
{ "Decode invalid keyword [throw error]",
json.decode, { ' [ "bad barewood", test ] ' },
false, { "Expected value but found invalid token at character 20" } },
{ "Decode invalid number #1 [throw error]",
json.decode, { '[ -+12 ]' },
false, { "Expected value but found invalid number at character 3" } },
{ "Decode invalid number #2 [throw error]",
json.decode, { '-v' },
false, { "Expected value but found invalid number at character 1" } },
{ "Decode invalid number exponent [throw error]",
json.decode, { '[ 0.4eg10 ]' },
false, { "Expected comma or array end but found invalid token at character 6" } },
-- Test decoding nested arrays / objects
{ "Set decode_max_depth(5)",
json.decode_max_depth, { 5 }, true, { 5 } },
{ "Decode array at nested limit",
json.decode, { '[[[[[ "nested" ]]]]]' },
true, { {{{{{ "nested" }}}}} } },
{ "Decode array over nested limit [throw error]",
json.decode, { '[[[[[[ "nested" ]]]]]]' },
false, { "Found too many nested data structures (6) at character 6" } },
{ "Decode object at nested limit",
json.decode, { '{"a":{"b":{"c":{"d":{"e":"nested"}}}}}' },
true, { {a={b={c={d={e="nested"}}}}} } },
{ "Decode object over nested limit [throw error]",
json.decode, { '{"a":{"b":{"c":{"d":{"e":{"f":"nested"}}}}}}' },
false, { "Found too many nested data structures (6) at character 26" } },
{ "Set decode_max_depth(1000)",
json.decode_max_depth, { 1000 }, true, { 1000 } },
{ "Decode deeply nested array [throw error]",
json.decode, { string.rep("[", 1100) .. '1100' .. string.rep("]", 1100)},
false, { "Found too many nested data structures (1001) at character 1001" } },
-- Test encoding nested tables
{ "Set encode_max_depth(5)",
json.encode_max_depth, { 5 }, true, { 5 } },
{ "Encode nested table as array at nested limit",
json.encode, { {{{{{"nested"}}}}} }, true, { '[[[[["nested"]]]]]' } },
{ "Encode nested table as array after nested limit [throw error]",
json.encode, { { {{{{{"nested"}}}}} } },
false, { "Cannot serialise, excessive nesting (6)" } },
{ "Encode nested table as object at nested limit",
json.encode, { {a={b={c={d={e="nested"}}}}} },
true, { '{"a":{"b":{"c":{"d":{"e":"nested"}}}}}' } },
{ "Encode nested table as object over nested limit [throw error]",
json.encode, { {a={b={c={d={e={f="nested"}}}}}} },
false, { "Cannot serialise, excessive nesting (6)" } },
{ "Encode table with cycle [throw error]",
json.encode, { testdata.table_cycle },
false, { "Cannot serialise, excessive nesting (6)" } },
{ "Set encode_max_depth(1000)",
json.encode_max_depth, { 1000 }, true, { 1000 } },
{ "Encode deeply nested data [throw error]",
json.encode, { testdata.deeply_nested_data },
false, { "Cannot serialise, excessive nesting (1001)" } },
-- Test encoding simple types
{ "Encode null",
json.encode, { json.null }, true, { 'null' } },
{ "Encode true",
json.encode, { true }, true, { 'true' } },
{ "Encode false",
json.encode, { false }, true, { 'false' } },
{ "Encode empty object",
json.encode, { { } }, true, { '{}' } },
{ "Encode integer",
json.encode, { 10 }, true, { '10' } },
{ "Encode string",
json.encode, { "hello" }, true, { '"hello"' } },
{ "Encode Lua function [throw error]",
json.encode, { function () end },
false, { "Cannot serialise function: type not supported" } },
-- Test decoding invalid numbers
{ "Set decode_invalid_numbers(true)",
json.decode_invalid_numbers, { true }, true, { true } },
{ "Decode hexadecimal",
json.decode, { '0x6.ffp1' }, true, { 13.9921875 } },
{ "Decode numbers with leading zero",
json.decode, { '[ 0123, 00.33 ]' }, true, { { 123, 0.33 } } },
{ "Decode +-Inf",
json.decode, { '[ +Inf, Inf, -Inf ]' }, true, { { Inf, Inf, -Inf } } },
{ "Decode +-Infinity",
json.decode, { '[ +Infinity, Infinity, -Infinity ]' },
true, { { Inf, Inf, -Inf } } },
{ "Decode +-NaN",
json.decode, { '[ +NaN, NaN, -NaN ]' }, true, { { NaN, NaN, NaN } } },
{ "Decode Infrared (not infinity) [throw error]",
json.decode, { 'Infrared' },
false, { "Expected the end but found invalid token at character 4" } },
{ "Decode Noodle (not NaN) [throw error]",
json.decode, { 'Noodle' },
false, { "Expected value but found invalid token at character 1" } },
{ "Set decode_invalid_numbers(false)",
json.decode_invalid_numbers, { false }, true, { false } },
{ "Decode hexadecimal [throw error]",
json.decode, { '0x6' },
false, { "Expected value but found invalid number at character 1" } },
{ "Decode numbers with leading zero [throw error]",
json.decode, { '[ 0123, 00.33 ]' },
false, { "Expected value but found invalid number at character 3" } },
{ "Decode +-Inf [throw error]",
json.decode, { '[ +Inf, Inf, -Inf ]' },
false, { "Expected value but found invalid token at character 3" } },
{ "Decode +-Infinity [throw error]",
json.decode, { '[ +Infinity, Infinity, -Infinity ]' },
false, { "Expected value but found invalid token at character 3" } },
{ "Decode +-NaN [throw error]",
json.decode, { '[ +NaN, NaN, -NaN ]' },
false, { "Expected value but found invalid token at character 3" } },
{ 'Set decode_invalid_numbers("on")',
json.decode_invalid_numbers, { "on" }, true, { true } },
-- Test encoding invalid numbers
{ "Set encode_invalid_numbers(false)",
json.encode_invalid_numbers, { false }, true, { false } },
{ "Encode NaN [throw error]",
json.encode, { NaN },
false, { "Cannot serialise number: must not be NaN or Infinity" } },
{ "Encode Infinity [throw error]",
json.encode, { Inf },
false, { "Cannot serialise number: must not be NaN or Infinity" } },
{ "Set encode_invalid_numbers(\"null\")",
json.encode_invalid_numbers, { "null" }, true, { "null" } },
{ "Encode NaN as null",
json.encode, { NaN }, true, { "null" } },
{ "Encode Infinity as null",
json.encode, { Inf }, true, { "null" } },
{ "Set encode_invalid_numbers(true)",
json.encode_invalid_numbers, { true }, true, { true } },
{ "Encode NaN",
json.encode, { NaN }, true, { "NaN" } },
{ "Encode +Infinity",
json.encode, { Inf }, true, { "Infinity" } },
{ "Encode -Infinity",
json.encode, { -Inf }, true, { "-Infinity" } },
{ 'Set encode_invalid_numbers("off")',
json.encode_invalid_numbers, { "off" }, true, { false } },
-- Test encoding tables
{ "Set encode_sparse_array(true, 2, 3)",
json.encode_sparse_array, { true, 2, 3 }, true, { true, 2, 3 } },
{ "Encode sparse table as array #1",
json.encode, { { [3] = "sparse test" } },
true, { '[null,null,"sparse test"]' } },
{ "Encode sparse table as array #2",
json.encode, { { [1] = "one", [4] = "sparse test" } },
true, { '["one",null,null,"sparse test"]' } },
{ "Encode sparse array as object",
json.encode, { { [1] = "one", [5] = "sparse test" } },
true, { '{"1":"one","5":"sparse test"}' } },
{ "Encode table with numeric string key as object",
json.encode, { { ["2"] = "numeric string key test" } },
true, { '{"2":"numeric string key test"}' } },
{ "Set encode_sparse_array(false)",
json.encode_sparse_array, { false }, true, { false, 2, 3 } },
{ "Encode table with incompatible key [throw error]",
json.encode, { { [false] = "wrong" } },
false, { "Cannot serialise boolean: table key must be a number or string" } },
-- Test escaping
{ "Encode all octets (8-bit clean)",
json.encode, { testdata.octets_raw }, true, { testdata.octets_escaped } },
{ "Decode all escaped octets",
json.decode, { testdata.octets_escaped }, true, { testdata.octets_raw } },
{ "Decode single UTF-16 escape",
json.decode, { [["\uF800"]] }, true, { "\239\160\128" } },
{ "Decode all UTF-16 escapes (including surrogate combinations)",
json.decode, { testdata.utf16_escaped }, true, { testdata.utf8_raw } },
{ "Decode swapped surrogate pair [throw error]",
json.decode, { [["\uDC00\uD800"]] },
false, { "Expected value but found invalid unicode escape code at character 2" } },
{ "Decode duplicate high surrogate [throw error]",
json.decode, { [["\uDB00\uDB00"]] },
false, { "Expected value but found invalid unicode escape code at character 2" } },
{ "Decode duplicate low surrogate [throw error]",
json.decode, { [["\uDB00\uDB00"]] },
false, { "Expected value but found invalid unicode escape code at character 2" } },
{ "Decode missing low surrogate [throw error]",
json.decode, { [["\uDB00"]] },
false, { "Expected value but found invalid unicode escape code at character 2" } },
{ "Decode invalid low surrogate [throw error]",
json.decode, { [["\uDB00\uD"]] },
false, { "Expected value but found invalid unicode escape code at character 2" } },
-- Test locale support
--
-- The standard Lua interpreter is ANSI C online doesn't support locales
-- by default. Force a known problematic locale to test strtod()/sprintf().
{ "Set locale to cs_CZ (comma separator)", function ()
os.setlocale("cs_CZ")
json.new()
end },
{ "Encode number under comma locale",
json.encode, { 1.5 }, true, { '1.5' } },
{ "Decode number in array under comma locale",
json.decode, { '[ 10, "test" ]' }, true, { { 10, "test" } } },
{ "Revert locale to POSIX", function ()
os.setlocale("C")
json.new()
end },
-- Test encode_keep_buffer() and enable_number_precision()
{ "Set encode_keep_buffer(false)",
json.encode_keep_buffer, { false }, true, { false } },
{ "Set encode_number_precision(3)",
json.encode_number_precision, { 3 }, true, { 3 } },
{ "Encode number with precision 3",
json.encode, { 1/3 }, true, { "0.333" } },
{ "Set encode_number_precision(14)",
json.encode_number_precision, { 14 }, true, { 14 } },
{ "Set encode_keep_buffer(true)",
json.encode_keep_buffer, { true }, true, { true } },
-- Test config API errors
-- Function is listed as '?' due to pcall
{ "Set encode_number_precision(0) [throw error]",
json.encode_number_precision, { 0 },
false, { "bad argument #1 to '?' (expected integer between 1 and 14)" } },
{ "Set encode_number_precision(\"five\") [throw error]",
json.encode_number_precision, { "five" },
false, { "bad argument #1 to '?' (number expected, got string)" } },
{ "Set encode_keep_buffer(nil, true) [throw error]",
json.encode_keep_buffer, { nil, true },
false, { "bad argument #2 to '?' (found too many arguments)" } },
{ "Set encode_max_depth(\"wrong\") [throw error]",
json.encode_max_depth, { "wrong" },
false, { "bad argument #1 to '?' (number expected, got string)" } },
{ "Set decode_max_depth(0) [throw error]",
json.decode_max_depth, { "0" },
false, { "bad argument #1 to '?' (expected integer between 1 and 2147483647)" } },
{ "Set encode_invalid_numbers(-2) [throw error]",
json.encode_invalid_numbers, { -2 },
false, { "bad argument #1 to '?' (invalid option '-2')" } },
{ "Set decode_invalid_numbers(true, false) [throw error]",
json.decode_invalid_numbers, { true, false },
false, { "bad argument #2 to '?' (found too many arguments)" } },
{ "Set encode_sparse_array(\"not quite on\") [throw error]",
json.encode_sparse_array, { "not quite on" },
false, { "bad argument #1 to '?' (invalid option 'not quite on')" } },
{ "Reset Lua CJSON configuration", function () json = json.new() end },
-- Wrap in a function to ensure the table returned by json.new() is used
{ "Check encode_sparse_array()",
function (...) return json.encode_sparse_array(...) end, { },
true, { false, 2, 10 } },
{ "Encode (safe) simple value",
json_safe.encode, { true },
true, { "true" } },
{ "Encode (safe) argument validation [throw error]",
json_safe.encode, { "arg1", "arg2" },
false, { "bad argument #1 to '?' (expected 1 argument)" } },
{ "Decode (safe) error generation",
json_safe.decode, { "Oops" },
true, { nil, "Expected value but found invalid token at character 1" } },
{ "Decode (safe) error generation after new()",
function(...) return json_safe.new().decode(...) end, { "Oops" },
true, { nil, "Expected value but found invalid token at character 1" } },
}
print(("==> Testing Lua CJSON version %s\n"):format(json._VERSION))
util.run_test_group(cjson_tests)
for _, filename in ipairs(arg) do
util.run_test("Decode cycle " .. filename, test_decode_cycle, { filename },
true, { true })
end
local pass, total = util.run_test_summary()
if pass == total then
print("==> Summary: all tests succeeded")
else
print(("==> Summary: %d/%d tests failed"):format(total - pass, total))
os.exit(1)
end
-- vi:ai et sw=4 ts=4:
{ "array": [ 10, true, null ] }
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
#define LUA_USE_BUILTIN_STRING // for string.xxx() #define LUA_USE_BUILTIN_STRING // for string.xxx()
#define LUA_USE_BUILTIN_TABLE // for table.xxx() #define LUA_USE_BUILTIN_TABLE // for table.xxx()
#define LUA_USE_BUILTIN_COROUTINE // for coroutine.xxx() #define LUA_USE_BUILTIN_COROUTINE // for coroutine.xxx()
#define LUA_USE_BUILTIN_MATH // for math.xxx(), partially work // #define LUA_USE_BUILTIN_MATH // for math.xxx(), partially work
// #define LUA_USE_BUILTIN_IO // for io.xxx(), partially work // #define LUA_USE_BUILTIN_IO // for io.xxx(), partially work
// #define LUA_USE_BUILTIN_OS // for os.xxx(), not work // #define LUA_USE_BUILTIN_OS // for os.xxx(), not work
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#define LUA_USE_MODULES_COAP #define LUA_USE_MODULES_COAP
#define LUA_USE_MODULES_U8G #define LUA_USE_MODULES_U8G
#define LUA_USE_MODULES_WS2812 #define LUA_USE_MODULES_WS2812
#define LUA_USE_MODULES_CJSON
#endif /* LUA_USE_MODULES */ #endif /* LUA_USE_MODULES */
#endif /* __USER_MODULES_H__ */ #endif /* __USER_MODULES_H__ */
...@@ -7,6 +7,6 @@ ...@@ -7,6 +7,6 @@
#define NODE_VERSION_INTERNAL 0U #define NODE_VERSION_INTERNAL 0U
#define NODE_VERSION "NodeMCU 0.9.5" #define NODE_VERSION "NodeMCU 0.9.5"
#define BUILD_DATE "build 20150315" #define BUILD_DATE "build 20150317"
#endif /* __USER_VERSION_H__ */ #endif /* __USER_VERSION_H__ */
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
#define c_strncmp os_strncmp #define c_strncmp os_strncmp
#define c_strncpy os_strncpy #define c_strncpy os_strncpy
// #define c_strstr os_strstr // #define c_strstr os_strstr
#define c_strncasecmp c_strcmp #define c_strncasecmp strncasecmp
#define c_strstr strstr #define c_strstr strstr
#define c_strncat strncat #define c_strncat strncat
......
...@@ -47,6 +47,7 @@ INCLUDES += -I ../platform ...@@ -47,6 +47,7 @@ INCLUDES += -I ../platform
INCLUDES += -I ../wofs INCLUDES += -I ../wofs
INCLUDES += -I ../spiffs INCLUDES += -I ../spiffs
INCLUDES += -I ../smart INCLUDES += -I ../smart
INCLUDES += -I ../cjson
PDIR := ../$(PDIR) PDIR := ../$(PDIR)
sinclude $(PDIR)Makefile sinclude $(PDIR)Makefile
...@@ -79,6 +79,9 @@ LUALIB_API int ( luaopen_file )( lua_State *L ); ...@@ -79,6 +79,9 @@ LUALIB_API int ( luaopen_file )( lua_State *L );
#define AUXLIB_OW "ow" #define AUXLIB_OW "ow"
LUALIB_API int ( luaopen_ow )( lua_State *L ); LUALIB_API int ( luaopen_ow )( lua_State *L );
#define AUXLIB_CJSON "cjson"
LUALIB_API int ( luaopen_ow )( lua_State *L );
// Helper macros // Helper macros
#define MOD_CHECK_ID( mod, id )\ #define MOD_CHECK_ID( mod, id )\
if( !platform_ ## mod ## _exists( id ) )\ if( !platform_ ## mod ## _exists( id ) )\
......
/* Lua CJSON - JSON support for Lua
*
* Copyright (c) 2010-2012 Mark Pulford <mark@kyne.com.au>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* Caveats:
* - JSON "null" values are represented as lightuserdata since Lua
* tables cannot contain "nil". Compare with cjson.null.
* - Invalid UTF-8 characters are not detected and will be passed
* untouched. If required, UTF-8 error checking should be done
* outside this library.
* - Javascript comments are not part of the JSON spec, and are not
* currently supported.
*
* Note: Decoding is slower than encoding. Lua spends significant
* time (30%) managing tables when parsing JSON since it is
* difficult to know object/array sizes ahead of time.
*/
// #include <assert.h>
#include "c_string.h"
#include "c_math.h"
#include "c_limits.h"
#include "lua.h"
#include "lauxlib.h"
#include "flash_api.h"
#include "strbuf.h"
#ifdef LUA_NUMBER_INTEGRAL
#include "fpconv.h"
#else
#define FPCONV_G_FMT_BUFSIZE 32
#define fpconv_strtod c_strtod
#define fpconv_init() ((void)0)
#endif
#ifndef CJSON_MODNAME
#define CJSON_MODNAME "cjson"
#endif
#ifndef CJSON_VERSION
#define CJSON_VERSION "2.1devel"
#endif
/* Workaround for Solaris platforms missing isinf() */
#if !defined(isinf) && (defined(USE_INTERNAL_ISINF) || defined(MISSING_ISINF))
#define isinf(x) (!isnan(x) && isnan((x) - (x)))
#endif
#define DEFAULT_SPARSE_CONVERT 0
#define DEFAULT_SPARSE_RATIO 2
#define DEFAULT_SPARSE_SAFE 10
#define DEFAULT_ENCODE_MAX_DEPTH 1000
#define DEFAULT_DECODE_MAX_DEPTH 1000
#define DEFAULT_ENCODE_INVALID_NUMBERS 0
#define DEFAULT_DECODE_INVALID_NUMBERS 1
#define DEFAULT_ENCODE_KEEP_BUFFER 0
#define DEFAULT_ENCODE_NUMBER_PRECISION 14
#ifdef DISABLE_INVALID_NUMBERS
#undef DEFAULT_DECODE_INVALID_NUMBERS
#define DEFAULT_DECODE_INVALID_NUMBERS 0
#endif
typedef enum {
T_OBJ_BEGIN,
T_OBJ_END,
T_ARR_BEGIN,
T_ARR_END,
T_STRING,
T_NUMBER,
T_BOOLEAN,
T_NULL,
T_COLON,
T_COMMA,
T_END,
T_WHITESPACE,
T_ERROR,
T_UNKNOWN
} json_token_type_t;
#if 0
static const char *json_token_type_name[] = {
"T_OBJ_BEGIN",
"T_OBJ_END",
"T_ARR_BEGIN",
"T_ARR_END",
"T_STRING",
"T_NUMBER",
"T_BOOLEAN",
"T_NULL",
"T_COLON",
"T_COMMA",
"T_END",
"T_WHITESPACE",
"T_ERROR",
"T_UNKNOWN",
NULL
};
#endif
static const char json_token_type_name[14][16] ICACHE_STORE_ATTR ICACHE_RODATA_ATTR = {
{'T','_','O','B','J','_','B','E','G','I','N',0},
{'T','_','O','B','J','_','E','N','D',0},
{'T','_','A','R','R','_','B','E','G','I','N',0},
{'T','_','A','R','R','_','E','N','D',0},
{'T','_','S','T','R','I','N','G',0},
{'T','_','N','U','M','B','E','R',0},
{'T','_','B','O','O','L','E','A','N',0},
{'T','_','N','U','L','L',0},
{'T','_','C','O','L','O','N',0},
{'T','_','C','O','M','M','A',0},
{'T','_','E','N','D',0},
{'T','_','W','H','I','T','E','S','P','A','C','E',0},
{'T','_','E','R','R','O','R',0},
{'T','_','U','N','K','N','O','W','N',0}
};
typedef struct {
// json_token_type_t ch2token[256]; // 256*4 = 1024 byte
// char escape2char[256]; /* Decoding */
/* encode_buf is only allocated and used when
* encode_keep_buffer is set */
strbuf_t encode_buf;
int encode_sparse_convert;
int encode_sparse_ratio;
int encode_sparse_safe;
int encode_max_depth;
int encode_invalid_numbers; /* 2 => Encode as "null" */
int encode_number_precision;
int encode_keep_buffer;
int decode_invalid_numbers;
int decode_max_depth;
} json_config_t;
typedef struct {
const char *data;
const char *ptr;
strbuf_t *tmp; /* Temporary storage for strings */
json_config_t *cfg;
int current_depth;
} json_parse_t;
typedef struct {
json_token_type_t type;
int index;
union {
const char *string;
double number;
int boolean;
} value;
int string_len;
} json_token_t;
#if 0
static const char *char2escape[256] = {
"\\u0000", "\\u0001", "\\u0002", "\\u0003",
"\\u0004", "\\u0005", "\\u0006", "\\u0007",
"\\b", "\\t", "\\n", "\\u000b",
"\\f", "\\r", "\\u000e", "\\u000f",
"\\u0010", "\\u0011", "\\u0012", "\\u0013",
"\\u0014", "\\u0015", "\\u0016", "\\u0017",
"\\u0018", "\\u0019", "\\u001a", "\\u001b",
"\\u001c", "\\u001d", "\\u001e", "\\u001f",
NULL, NULL, "\\\"", NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\/",
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, "\\\\", NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\u007f",
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
};
#endif
/* ===== HELPER FUNCTION ===== */
static const char escape_array[36][8] ICACHE_STORE_ATTR ICACHE_RODATA_ATTR = {
{'\\','u','0','0','0','0','\0','\0'},
{'\\','u','0','0','0','1','\0','\0'},
{'\\','u','0','0','0','2','\0','\0'},
{'\\','u','0','0','0','3','\0','\0'},
{'\\','u','0','0','0','4','\0','\0'},
{'\\','u','0','0','0','5','\0','\0'},
{'\\','u','0','0','0','6','\0','\0'},
{'\\','u','0','0','0','7','\0','\0'},
{'\\','b','\0','\0','\0','\0','\0','\0'},
{'\\','t','\0','\0','\0','\0','\0','\0'},
{'\\','n','\0','\0','\0','\0','\0','\0'},
{'\\','u','0','0','0','b','\0','\0'},
{'\\','f','\0','\0','\0','\0','\0','\0'},
{'\\','r','\0','\0','\0','\0','\0','\0'},
{'\\','u','0','0','0','e','\0','\0'},
{'\\','u','0','0','0','f','\0','\0'},
{'\\','u','0','0','1','0','\0','\0'},
{'\\','u','0','0','1','1','\0','\0'},
{'\\','u','0','0','1','2','\0','\0'},
{'\\','u','0','0','1','3','\0','\0'},
{'\\','u','0','0','1','4','\0','\0'},
{'\\','u','0','0','1','5','\0','\0'},
{'\\','u','0','0','1','6','\0','\0'},
{'\\','u','0','0','1','7','\0','\0'},
{'\\','u','0','0','1','8','\0','\0'},
{'\\','u','0','0','1','9','\0','\0'},
{'\\','u','0','0','1','a','\0','\0'},
{'\\','u','0','0','1','b','\0','\0'},
{'\\','u','0','0','1','c','\0','\0'},
{'\\','u','0','0','1','d','\0','\0'},
{'\\','u','0','0','1','e','\0','\0'},
{'\\','u','0','0','1','f','\0','\0'},
{'\\','\"','\0','\0','\0','\0','\0','\0'},
{'\\','/','\0','\0','\0','\0','\0','\0'},
{'\\','\\','\0','\0','\0','\0','\0','\0'},
{'\\','u','0','0','7','f','\0','\0'}
};
static const char *char2escape(unsigned char c){
if(c<32) return escape_array[c];
switch(c){
case 34: return escape_array[32];
case 47: return escape_array[33];
case 92: return escape_array[34];
case 127: return escape_array[35];
default:
return NULL;
}
}
static json_token_type_t ch2token(unsigned char c){
switch(c){
case '{': return T_OBJ_BEGIN;
case '}': return T_OBJ_END;
case '[': return T_ARR_BEGIN;
case ']': return T_ARR_END;
case ',': return T_COMMA;
case ':': return T_COLON;
case '\0': return T_END;
case ' ': return T_WHITESPACE;
case '\t': return T_WHITESPACE;
case '\n': return T_WHITESPACE;
case '\r': return T_WHITESPACE;
/* Update characters that require further processing */
case 'f': case 'i': case 'I': case 'n': case 'N': case 't': case '"': case '+': case '-':
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
return T_UNKNOWN;
default:
return T_ERROR;
}
}
static char escape2char(unsigned char c){
switch(c){
case '"': return '"';
case '\\': return '\\';
case '/': return '/';
case 'b': return '\b';
case 't': return '\t';
case 'n': return '\n';
case 'f': return '\f';
case 'r': return '\r';
case 'u': return 'u';
default:
return 0;
}
}
/* ===== CONFIGURATION ===== */
#if 0
static json_config_t *json_fetch_config(lua_State *l)
{
json_config_t *cfg;
cfg = lua_touserdata(l, lua_upvalueindex(1));
if (!cfg)
luaL_error(l, "BUG: Unable to fetch CJSON configuration");
return cfg;
}
/* Ensure the correct number of arguments have been provided.
* Pad with nil to allow other functions to simply check arg[i]
* to find whether an argument was provided */
static json_config_t *json_arg_init(lua_State *l, int args)
{
luaL_argcheck(l, lua_gettop(l) <= args, args + 1,
"found too many arguments");
while (lua_gettop(l) < args)
lua_pushnil(l);
return json_fetch_config(l);
}
/* Process integer options for configuration functions */
static int json_integer_option(lua_State *l, int optindex, int *setting,
int min, int max)
{
char errmsg[64];
int value;
if (!lua_isnil(l, optindex)) {
value = luaL_checkinteger(l, optindex);
c_sprintf(errmsg, "expected integer between %d and %d", min, max);
luaL_argcheck(l, min <= value && value <= max, 1, errmsg);
*setting = value;
}
lua_pushinteger(l, *setting);
return 1;
}
/* Process enumerated arguments for a configuration function */
static int json_enum_option(lua_State *l, int optindex, int *setting,
const char **options, int bool_true)
{
static const char *bool_options[] = { "off", "on", NULL };
if (!options) {
options = bool_options;
bool_true = 1;
}
if (!lua_isnil(l, optindex)) {
if (bool_true && lua_isboolean(l, optindex))
*setting = lua_toboolean(l, optindex) * bool_true;
else
*setting = luaL_checkoption(l, optindex, NULL, options);
}
if (bool_true && (*setting == 0 || *setting == bool_true))
lua_pushboolean(l, *setting);
else
lua_pushstring(l, options[*setting]);
return 1;
}
/* Configures handling of extremely sparse arrays:
* convert: Convert extremely sparse arrays into objects? Otherwise error.
* ratio: 0: always allow sparse; 1: never allow sparse; >1: use ratio
* safe: Always use an array when the max index <= safe */
static int json_cfg_encode_sparse_array(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 3);
json_enum_option(l, 1, &cfg->encode_sparse_convert, NULL, 1);
json_integer_option(l, 2, &cfg->encode_sparse_ratio, 0, INT_MAX);
json_integer_option(l, 3, &cfg->encode_sparse_safe, 0, INT_MAX);
return 3;
}
/* Configures the maximum number of nested arrays/objects allowed when
* encoding */
static int json_cfg_encode_max_depth(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
return json_integer_option(l, 1, &cfg->encode_max_depth, 1, INT_MAX);
}
/* Configures the maximum number of nested arrays/objects allowed when
* encoding */
static int json_cfg_decode_max_depth(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
return json_integer_option(l, 1, &cfg->decode_max_depth, 1, INT_MAX);
}
/* Configures number precision when converting doubles to text */
static int json_cfg_encode_number_precision(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
return json_integer_option(l, 1, &cfg->encode_number_precision, 1, 14);
}
/* Configures JSON encoding buffer persistence */
static int json_cfg_encode_keep_buffer(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
int old_value;
old_value = cfg->encode_keep_buffer;
json_enum_option(l, 1, &cfg->encode_keep_buffer, NULL, 1);
/* Init / free the buffer if the setting has changed */
if (old_value ^ cfg->encode_keep_buffer) {
if (cfg->encode_keep_buffer){
if(-1==strbuf_init(&cfg->encode_buf, 0))
return luaL_error(l, "not enough memory");
}
else
strbuf_free(&cfg->encode_buf);
}
return 1;
}
#if defined(DISABLE_INVALID_NUMBERS) && !defined(USE_INTERNAL_FPCONV)
void json_verify_invalid_number_setting(lua_State *l, int *setting)
{
if (*setting == 1) {
*setting = 0;
luaL_error(l, "Infinity, NaN, and/or hexadecimal numbers are not supported.");
}
}
#else
#define json_verify_invalid_number_setting(l, s) do { } while(0)
#endif
static int json_cfg_encode_invalid_numbers(lua_State *l)
{
static const char *options[] = { "off", "on", "null", NULL };
json_config_t *cfg = json_arg_init(l, 1);
json_enum_option(l, 1, &cfg->encode_invalid_numbers, options, 1);
json_verify_invalid_number_setting(l, &cfg->encode_invalid_numbers);
return 1;
}
static int json_cfg_decode_invalid_numbers(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
json_enum_option(l, 1, &cfg->decode_invalid_numbers, NULL, 1);
json_verify_invalid_number_setting(l, &cfg->encode_invalid_numbers);
return 1;
}
static int json_destroy_config(lua_State *l)
{
json_config_t *cfg;
cfg = lua_touserdata(l, 1);
if (cfg)
strbuf_free(&cfg->encode_buf);
cfg = NULL;
return 0;
}
static void json_create_config(lua_State *l)
{
json_config_t *cfg;
int i;
cfg = lua_newuserdata(l, sizeof(*cfg));
/* Create GC method to clean up strbuf */
lua_newtable(l);
lua_pushcfunction(l, json_destroy_config);
lua_setfield(l, -2, "__gc");
lua_setmetatable(l, -2);
cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT;
cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO;
cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE;
cfg->encode_max_depth = DEFAULT_ENCODE_MAX_DEPTH;
cfg->decode_max_depth = DEFAULT_DECODE_MAX_DEPTH;
cfg->encode_invalid_numbers = DEFAULT_ENCODE_INVALID_NUMBERS;
cfg->decode_invalid_numbers = DEFAULT_DECODE_INVALID_NUMBERS;
cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER;
cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION;
#if DEFAULT_ENCODE_KEEP_BUFFER > 0
strbuf_init(&cfg->encode_buf, 0);
#endif
/* Decoding init */
/* Tag all characters as an error */
for (i = 0; i < 256; i++)
cfg->ch2token[i] = T_ERROR;
/* Set tokens that require no further processing */
cfg->ch2token['{'] = T_OBJ_BEGIN;
cfg->ch2token['}'] = T_OBJ_END;
cfg->ch2token['['] = T_ARR_BEGIN;
cfg->ch2token[']'] = T_ARR_END;
cfg->ch2token[','] = T_COMMA;
cfg->ch2token[':'] = T_COLON;
cfg->ch2token['\0'] = T_END;
cfg->ch2token[' '] = T_WHITESPACE;
cfg->ch2token['\t'] = T_WHITESPACE;
cfg->ch2token['\n'] = T_WHITESPACE;
cfg->ch2token['\r'] = T_WHITESPACE;
/* Update characters that require further processing */
cfg->ch2token['f'] = T_UNKNOWN; /* false? */
cfg->ch2token['i'] = T_UNKNOWN; /* inf, ininity? */
cfg->ch2token['I'] = T_UNKNOWN;
cfg->ch2token['n'] = T_UNKNOWN; /* null, nan? */
cfg->ch2token['N'] = T_UNKNOWN;
cfg->ch2token['t'] = T_UNKNOWN; /* true? */
cfg->ch2token['"'] = T_UNKNOWN; /* string? */
cfg->ch2token['+'] = T_UNKNOWN; /* number? */
cfg->ch2token['-'] = T_UNKNOWN;
for (i = 0; i < 10; i++)
cfg->ch2token['0' + i] = T_UNKNOWN;
/* Lookup table for parsing escape characters */
for (i = 0; i < 256; i++)
cfg->escape2char[i] = 0; /* String error */
cfg->escape2char['"'] = '"';
cfg->escape2char['\\'] = '\\';
cfg->escape2char['/'] = '/';
cfg->escape2char['b'] = '\b';
cfg->escape2char['t'] = '\t';
cfg->escape2char['n'] = '\n';
cfg->escape2char['f'] = '\f';
cfg->escape2char['r'] = '\r';
cfg->escape2char['u'] = 'u'; /* Unicode parsing required */
}
#endif
json_config_t _cfg;
static json_config_t *json_fetch_config(lua_State *l)
{
return &_cfg;
}
static int cfg_init(json_config_t *cfg){
cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT;
cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO;
cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE;
cfg->encode_max_depth = DEFAULT_ENCODE_MAX_DEPTH;
cfg->decode_max_depth = DEFAULT_DECODE_MAX_DEPTH;
cfg->encode_invalid_numbers = DEFAULT_ENCODE_INVALID_NUMBERS;
cfg->decode_invalid_numbers = DEFAULT_DECODE_INVALID_NUMBERS;
cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER;
cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION;
#if DEFAULT_ENCODE_KEEP_BUFFER > 0
if(-1==strbuf_init(&cfg->encode_buf, 0)){
NODE_ERR("not enough memory\n");
return -1;
}
#endif
return 0;
}
/* ===== ENCODING ===== */
static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *json, int lindex,
const char *reason)
{
if (!cfg->encode_keep_buffer)
strbuf_free(json);
luaL_error(l, "Cannot serialise %s: %s",
lua_typename(l, lua_type(l, lindex)), reason);
}
/* json_append_string args:
* - lua_State
* - JSON strbuf
* - String (Lua stack index)
*
* Returns nothing. Doesn't remove string from Lua stack */
static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
{
const char *escstr;
int i;
const char *str;
size_t len;
str = lua_tolstring(l, lindex, &len);
/* Worst case is len * 6 (all unicode escapes).
* This buffer is reused constantly for small strings
* If there are any excess pages, they won't be hit anyway.
* This gains ~5% speedup. */
strbuf_ensure_empty_length(json, len * 6 + 2);
strbuf_append_char_unsafe(json, '\"');
for (i = 0; i < len; i++) {
escstr = char2escape((unsigned char)str[i]);
if (escstr){
int i;
char temp[8]; // for now, 8-bytes is enough.
for (i=0; i < 8; ++i)
{
temp[i] = byte_of_aligned_array(escstr, i);
if(temp[i]==0) break;
}
escstr = temp;
strbuf_append_string(json, escstr);
}
else
strbuf_append_char_unsafe(json, str[i]);
}
strbuf_append_char_unsafe(json, '\"');
}
/* Find the size of the array on the top of the Lua stack
* -1 object (not a pure array)
* >=0 elements in array
*/
static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json)
{
double k;
int max;
int items;
max = 0;
items = 0;
lua_pushnil(l);
/* table, startkey */
while (lua_next(l, -2) != 0) {
/* table, key, value */
if (lua_type(l, -2) == LUA_TNUMBER &&
(k = lua_tonumber(l, -2))) {
/* Integer >= 1 ? */
if (floor(k) == k && k >= 1) {
if (k > max)
max = k;
items++;
lua_pop(l, 1);
continue;
}
}
/* Must not be an array (non integer key) */
lua_pop(l, 2);
return -1;
}
/* Encode excessively sparse arrays as objects (if enabled) */
if (cfg->encode_sparse_ratio > 0 &&
max > items * cfg->encode_sparse_ratio &&
max > cfg->encode_sparse_safe) {
if (!cfg->encode_sparse_convert)
json_encode_exception(l, cfg, json, -1, "excessively sparse array");
return -1;
}
return max;
}
static void json_check_encode_depth(lua_State *l, json_config_t *cfg,
int current_depth, strbuf_t *json)
{
/* Ensure there are enough slots free to traverse a table (key,
* value) and push a string for a potential error message.
*
* Unlike "decode", the key and value are still on the stack when
* lua_checkstack() is called. Hence an extra slot for luaL_error()
* below is required just in case the next check to lua_checkstack()
* fails.
*
* While this won't cause a crash due to the EXTRA_STACK reserve
* slots, it would still be an improper use of the API. */
if (current_depth <= cfg->encode_max_depth && lua_checkstack(l, 3))
return;
if (!cfg->encode_keep_buffer)
strbuf_free(json);
luaL_error(l, "Cannot serialise, excessive nesting (%d)",
current_depth);
}
static void json_append_data(lua_State *l, json_config_t *cfg,
int current_depth, strbuf_t *json);
/* json_append_array args:
* - lua_State
* - JSON strbuf
* - Size of passwd Lua array (top of stack) */
static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth,
strbuf_t *json, int array_length)
{
int comma, i;
strbuf_append_char(json, '[');
comma = 0;
for (i = 1; i <= array_length; i++) {
if (comma)
strbuf_append_char(json, ',');
else
comma = 1;
lua_rawgeti(l, -1, i);
json_append_data(l, cfg, current_depth, json);
lua_pop(l, 1);
}
strbuf_append_char(json, ']');
}
static void json_append_number(lua_State *l, json_config_t *cfg,
strbuf_t *json, int lindex)
{
double num = lua_tonumber(l, lindex);
int len;
if (cfg->encode_invalid_numbers == 0) {
/* Prevent encoding invalid numbers */
if (isinf(num) || isnan(num))
json_encode_exception(l, cfg, json, lindex,
"must not be NaN or Infinity");
} else if (cfg->encode_invalid_numbers == 1) {
/* Encode NaN/Infinity separately to ensure Javascript compatible
* values are used. */
if (isnan(num)) {
strbuf_append_mem(json, "NaN", 3);
return;
}
if (isinf(num)) {
if (num < 0)
strbuf_append_mem(json, "-Infinity", 9);
else
strbuf_append_mem(json, "Infinity", 8);
return;
}
} else {
/* Encode invalid numbers as "null" */
if (isinf(num) || isnan(num)) {
strbuf_append_mem(json, "null", 4);
return;
}
}
strbuf_ensure_empty_length(json, FPCONV_G_FMT_BUFSIZE);
#ifdef LUA_NUMBER_INTEGRAL
len = fpconv_g_fmt(strbuf_empty_ptr(json), num, cfg->encode_number_precision);
#else
c_sprintf(strbuf_empty_ptr(json), "%.14g", num);
len = c_strlen(strbuf_empty_ptr(json));
#endif
strbuf_extend_length(json, len);
}
static void json_append_object(lua_State *l, json_config_t *cfg,
int current_depth, strbuf_t *json)
{
int comma, keytype;
/* Object */
strbuf_append_char(json, '{');
lua_pushnil(l);
/* table, startkey */
comma = 0;
while (lua_next(l, -2) != 0) {
if (comma)
strbuf_append_char(json, ',');
else
comma = 1;
/* table, key, value */
keytype = lua_type(l, -2);
if (keytype == LUA_TNUMBER) {
strbuf_append_char(json, '"');
json_append_number(l, cfg, json, -2);
strbuf_append_mem(json, "\":", 2);
} else if (keytype == LUA_TSTRING) {
json_append_string(l, json, -2);
strbuf_append_char(json, ':');
} else {
json_encode_exception(l, cfg, json, -2,
"table key must be a number or string");
/* never returns */
}
/* table, key, value */
json_append_data(l, cfg, current_depth, json);
lua_pop(l, 1);
/* table, key */
}
strbuf_append_char(json, '}');
}
/* Serialise Lua data into JSON string. */
static void json_append_data(lua_State *l, json_config_t *cfg,
int current_depth, strbuf_t *json)
{
int len;
switch (lua_type(l, -1)) {
case LUA_TSTRING:
json_append_string(l, json, -1);
break;
case LUA_TNUMBER:
json_append_number(l, cfg, json, -1);
break;
case LUA_TBOOLEAN:
if (lua_toboolean(l, -1))
strbuf_append_mem(json, "true", 4);
else
strbuf_append_mem(json, "false", 5);
break;
case LUA_TTABLE:
current_depth++;
json_check_encode_depth(l, cfg, current_depth, json);
len = lua_array_length(l, cfg, json);
if (len > 0)
json_append_array(l, cfg, current_depth, json, len);
else
json_append_object(l, cfg, current_depth, json);
break;
case LUA_TNIL:
strbuf_append_mem(json, "null", 4);
break;
case LUA_TLIGHTUSERDATA:
if (lua_touserdata(l, -1) == NULL) {
strbuf_append_mem(json, "null", 4);
break;
}
default:
/* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD,
* and LUA_TLIGHTUSERDATA) cannot be serialised */
json_encode_exception(l, cfg, json, -1, "type not supported");
/* never returns */
}
}
static int json_encode(lua_State *l)
{
json_config_t *cfg = json_fetch_config(l);
strbuf_t local_encode_buf;
strbuf_t *encode_buf;
char *json;
int len;
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
if (!cfg->encode_keep_buffer) {
/* Use private buffer */
encode_buf = &local_encode_buf;
if(-1==strbuf_init(encode_buf, 0))
return luaL_error(l, "not enough memory");
} else {
/* Reuse existing buffer */
encode_buf = &cfg->encode_buf;
strbuf_reset(encode_buf);
}
json_append_data(l, cfg, 0, encode_buf);
json = strbuf_string(encode_buf, &len);
lua_pushlstring(l, json, len);
if (!cfg->encode_keep_buffer)
strbuf_free(encode_buf);
return 1;
}
/* ===== DECODING ===== */
static void json_process_value(lua_State *l, json_parse_t *json,
json_token_t *token);
static int hexdigit2int(char hex)
{
if ('0' <= hex && hex <= '9')
return hex - '0';
/* Force lowercase */
hex |= 0x20;
if ('a' <= hex && hex <= 'f')
return 10 + hex - 'a';
return -1;
}
static int decode_hex4(const char *hex)
{
int digit[4];
int i;
/* Convert ASCII hex digit to numeric digit
* Note: this returns an error for invalid hex digits, including
* NULL */
for (i = 0; i < 4; i++) {
digit[i] = hexdigit2int(hex[i]);
if (digit[i] < 0) {
return -1;
}
}
return (digit[0] << 12) +
(digit[1] << 8) +
(digit[2] << 4) +
digit[3];
}
/* Converts a Unicode codepoint to UTF-8.
* Returns UTF-8 string length, and up to 4 bytes in *utf8 */
static int codepoint_to_utf8(char *utf8, int codepoint)
{
/* 0xxxxxxx */
if (codepoint <= 0x7F) {
utf8[0] = codepoint;
return 1;
}
/* 110xxxxx 10xxxxxx */
if (codepoint <= 0x7FF) {
utf8[0] = (codepoint >> 6) | 0xC0;
utf8[1] = (codepoint & 0x3F) | 0x80;
return 2;
}
/* 1110xxxx 10xxxxxx 10xxxxxx */
if (codepoint <= 0xFFFF) {
utf8[0] = (codepoint >> 12) | 0xE0;
utf8[1] = ((codepoint >> 6) & 0x3F) | 0x80;
utf8[2] = (codepoint & 0x3F) | 0x80;
return 3;
}
/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
if (codepoint <= 0x1FFFFF) {
utf8[0] = (codepoint >> 18) | 0xF0;
utf8[1] = ((codepoint >> 12) & 0x3F) | 0x80;
utf8[2] = ((codepoint >> 6) & 0x3F) | 0x80;
utf8[3] = (codepoint & 0x3F) | 0x80;
return 4;
}
return 0;
}
/* Called when index pointing to beginning of UTF-16 code escape: \uXXXX
* \u is guaranteed to exist, but the remaining hex characters may be
* missing.
* Translate to UTF-8 and append to temporary token string.
* Must advance index to the next character to be processed.
* Returns: 0 success
* -1 error
*/
static int json_append_unicode_escape(json_parse_t *json)
{
char utf8[4]; /* Surrogate pairs require 4 UTF-8 bytes */
int codepoint;
int surrogate_low;
int len;
int escape_len = 6;
/* Fetch UTF-16 code unit */
codepoint = decode_hex4(json->ptr + 2);
if (codepoint < 0)
return -1;
/* UTF-16 surrogate pairs take the following 2 byte form:
* 11011 x yyyyyyyyyy
* When x = 0: y is the high 10 bits of the codepoint
* x = 1: y is the low 10 bits of the codepoint
*
* Check for a surrogate pair (high or low) */
if ((codepoint & 0xF800) == 0xD800) {
/* Error if the 1st surrogate is not high */
if (codepoint & 0x400)
return -1;
/* Ensure the next code is a unicode escape */
if (*(json->ptr + escape_len) != '\\' ||
*(json->ptr + escape_len + 1) != 'u') {
return -1;
}
/* Fetch the next codepoint */
surrogate_low = decode_hex4(json->ptr + 2 + escape_len);
if (surrogate_low < 0)
return -1;
/* Error if the 2nd code is not a low surrogate */
if ((surrogate_low & 0xFC00) != 0xDC00)
return -1;
/* Calculate Unicode codepoint */
codepoint = (codepoint & 0x3FF) << 10;
surrogate_low &= 0x3FF;
codepoint = (codepoint | surrogate_low) + 0x10000;
escape_len = 12;
}
/* Convert codepoint to UTF-8 */
len = codepoint_to_utf8(utf8, codepoint);
if (!len)
return -1;
/* Append bytes and advance parse index */
strbuf_append_mem_unsafe(json->tmp, utf8, len);
json->ptr += escape_len;
return 0;
}
static void json_set_token_error(json_token_t *token, json_parse_t *json,
const char *errtype)
{
token->type = T_ERROR;
token->index = json->ptr - json->data;
token->value.string = errtype;
}
static void json_next_string_token(json_parse_t *json, json_token_t *token)
{
// char *escape2char = json->cfg->escape2char;
char ch;
/* Caller must ensure a string is next */
if(!(*json->ptr == '"')) return;
/* Skip " */
json->ptr++;
/* json->tmp is the temporary strbuf used to accumulate the
* decoded string value.
* json->tmp is sized to handle JSON containing only a string value.
*/
strbuf_reset(json->tmp);
while ((ch = *json->ptr) != '"') {
if (!ch) {
/* Premature end of the string */
json_set_token_error(token, json, "unexpected end of string");
return;
}
/* Handle escapes */
if (ch == '\\') {
/* Fetch escape character */
ch = *(json->ptr + 1);
/* Translate escape code and append to tmp string */
ch = escape2char((unsigned char)ch);
if (ch == 'u') {
if (json_append_unicode_escape(json) == 0)
continue;
json_set_token_error(token, json,
"invalid unicode escape code");
return;
}
if (!ch) {
json_set_token_error(token, json, "invalid escape code");
return;
}
/* Skip '\' */
json->ptr++;
}
/* Append normal character or translated single character
* Unicode escapes are handled above */
strbuf_append_char_unsafe(json->tmp, ch);
json->ptr++;
}
json->ptr++; /* Eat final quote (") */
strbuf_ensure_null(json->tmp);
token->type = T_STRING;
token->value.string = strbuf_string(json->tmp, &token->string_len);
}
/* JSON numbers should take the following form:
* -?(0|[1-9]|[1-9][0-9]+)(.[0-9]+)?([eE][-+]?[0-9]+)?
*
* json_next_number_token() uses strtod() which allows other forms:
* - numbers starting with '+'
* - NaN, -NaN, infinity, -infinity
* - hexadecimal numbers
* - numbers with leading zeros
*
* json_is_invalid_number() detects "numbers" which may pass strtod()'s
* error checking, but should not be allowed with strict JSON.
*
* json_is_invalid_number() may pass numbers which cause strtod()
* to generate an error.
*/
static int json_is_invalid_number(json_parse_t *json)
{
const char *p = json->ptr;
/* Reject numbers starting with + */
if (*p == '+')
return 1;
/* Skip minus sign if it exists */
if (*p == '-')
p++;
/* Reject numbers starting with 0x, or leading zeros */
if (*p == '0') {
int ch2 = *(p + 1);
if ((ch2 | 0x20) == 'x' || /* Hex */
('0' <= ch2 && ch2 <= '9')) /* Leading zero */
return 1;
return 0;
} else if (*p <= '9') {
return 0; /* Ordinary number */
}
char tmp[4]; // conv to lower. because c_strncasecmp == c_strcmp
int i;
for (i = 0; i < 3; ++i)
{
if(p[i]!=0)
tmp[i] = tolower(p[i]);
else
tmp[i] = 0;
}
tmp[3] = 0;
/* Reject inf/nan */
if (!c_strncasecmp(tmp, "inf", 3))
return 1;
if (!c_strncasecmp(tmp, "nan", 3))
return 1;
/* Pass all other numbers which may still be invalid, but
* strtod() will catch them. */
return 0;
}
static void json_next_number_token(json_parse_t *json, json_token_t *token)
{
char *endptr;
token->type = T_NUMBER;
token->value.number = fpconv_strtod(json->ptr, &endptr);
if (json->ptr == endptr)
json_set_token_error(token, json, "invalid number");
else
json->ptr = endptr; /* Skip the processed number */
return;
}
/* Fills in the token struct.
* T_STRING will return a pointer to the json_parse_t temporary string
* T_ERROR will leave the json->ptr pointer at the error.
*/
static void json_next_token(json_parse_t *json, json_token_t *token)
{
// const json_token_type_t *ch2token = json->cfg->ch2token;
int ch;
/* Eat whitespace. */
while (1) {
ch = (unsigned char)*(json->ptr);
token->type = ch2token(ch);
if (token->type != T_WHITESPACE)
break;
json->ptr++;
}
/* Store location of new token. Required when throwing errors
* for unexpected tokens (syntax errors). */
token->index = json->ptr - json->data;
/* Don't advance the pointer for an error or the end */
if (token->type == T_ERROR) {
json_set_token_error(token, json, "invalid token");
return;
}
if (token->type == T_END) {
return;
}
/* Found a known single character token, advance index and return */
if (token->type != T_UNKNOWN) {
json->ptr++;
return;
}
/* Process characters which triggered T_UNKNOWN
*
* Must use strncmp() to match the front of the JSON string.
* JSON identifier must be lowercase.
* When strict_numbers if disabled, either case is allowed for
* Infinity/NaN (since we are no longer following the spec..) */
if (ch == '"') {
json_next_string_token(json, token);
return;
} else if (ch == '-' || ('0' <= ch && ch <= '9')) {
if (!json->cfg->decode_invalid_numbers && json_is_invalid_number(json)) {
json_set_token_error(token, json, "invalid number");
return;
}
json_next_number_token(json, token);
return;
} else if (!c_strncmp(json->ptr, "true", 4)) {
token->type = T_BOOLEAN;
token->value.boolean = 1;
json->ptr += 4;
return;
} else if (!c_strncmp(json->ptr, "false", 5)) {
token->type = T_BOOLEAN;
token->value.boolean = 0;
json->ptr += 5;
return;
} else if (!c_strncmp(json->ptr, "null", 4)) {
token->type = T_NULL;
json->ptr += 4;
return;
} else if (json->cfg->decode_invalid_numbers &&
json_is_invalid_number(json)) {
/* When decode_invalid_numbers is enabled, only attempt to process
* numbers we know are invalid JSON (Inf, NaN, hex)
* This is required to generate an appropriate token error,
* otherwise all bad tokens will register as "invalid number"
*/
json_next_number_token(json, token);
return;
}
/* Token starts with t/f/n but isn't recognised above. */
json_set_token_error(token, json, "invalid token");
}
/* This function does not return.
* DO NOT CALL WITH DYNAMIC MEMORY ALLOCATED.
* The only supported exception is the temporary parser string
* json->tmp struct.
* json and token should exist on the stack somewhere.
* luaL_error() will long_jmp and release the stack */
static void json_throw_parse_error(lua_State *l, json_parse_t *json,
const char *exp, json_token_t *token)
{
const char *found;
char temp[16]; // for now, 16-bytes is enough.
strbuf_free(json->tmp);
if (token->type == T_ERROR)
found = token->value.string;
else
{
found = json_token_type_name[token->type];
int i;
for (i=0; i < 16; ++i)
{
temp[i] = byte_of_aligned_array(found, i);
if(temp[i]==0) break;
}
found = temp;
}
/* Note: token->index is 0 based, display starting from 1 */
luaL_error(l, "Expected %s but found %s at character %d",
exp, found, token->index + 1);
}
static inline void json_decode_ascend(json_parse_t *json)
{
json->current_depth--;
}
static void json_decode_descend(lua_State *l, json_parse_t *json, int slots)
{
json->current_depth++;
if (json->current_depth <= json->cfg->decode_max_depth &&
lua_checkstack(l, slots)) {
return;
}
strbuf_free(json->tmp);
luaL_error(l, "Found too many nested data structures (%d) at character %d",
json->current_depth, json->ptr - json->data);
}
static void json_parse_object_context(lua_State *l, json_parse_t *json)
{
json_token_t token;
/* 3 slots required:
* .., table, key, value */
json_decode_descend(l, json, 3);
lua_newtable(l);
json_next_token(json, &token);
/* Handle empty objects */
if (token.type == T_OBJ_END) {
json_decode_ascend(json);
return;
}
while (1) {
if (token.type != T_STRING)
json_throw_parse_error(l, json, "object key string", &token);
/* Push key */
lua_pushlstring(l, token.value.string, token.string_len);
json_next_token(json, &token);
if (token.type != T_COLON)
json_throw_parse_error(l, json, "colon", &token);
/* Fetch value */
json_next_token(json, &token);
json_process_value(l, json, &token);
/* Set key = value */
lua_rawset(l, -3);
json_next_token(json, &token);
if (token.type == T_OBJ_END) {
json_decode_ascend(json);
return;
}
if (token.type != T_COMMA)
json_throw_parse_error(l, json, "comma or object end", &token);
json_next_token(json, &token);
}
}
/* Handle the array context */
static void json_parse_array_context(lua_State *l, json_parse_t *json)
{
json_token_t token;
int i;
/* 2 slots required:
* .., table, value */
json_decode_descend(l, json, 2);
lua_newtable(l);
json_next_token(json, &token);
/* Handle empty arrays */
if (token.type == T_ARR_END) {
json_decode_ascend(json);
return;
}
for (i = 1; ; i++) {
json_process_value(l, json, &token);
lua_rawseti(l, -2, i); /* arr[i] = value */
json_next_token(json, &token);
if (token.type == T_ARR_END) {
json_decode_ascend(json);
return;
}
if (token.type != T_COMMA)
json_throw_parse_error(l, json, "comma or array end", &token);
json_next_token(json, &token);
}
}
/* Handle the "value" context */
static void json_process_value(lua_State *l, json_parse_t *json,
json_token_t *token)
{
switch (token->type) {
case T_STRING:
lua_pushlstring(l, token->value.string, token->string_len);
break;;
case T_NUMBER:
lua_pushnumber(l, token->value.number);
break;;
case T_BOOLEAN:
lua_pushboolean(l, token->value.boolean);
break;;
case T_OBJ_BEGIN:
json_parse_object_context(l, json);
break;;
case T_ARR_BEGIN:
json_parse_array_context(l, json);
break;;
case T_NULL:
/* In Lua, setting "t[k] = nil" will delete k from the table.
* Hence a NULL pointer lightuserdata object is used instead */
lua_pushlightuserdata(l, NULL);
break;;
default:
json_throw_parse_error(l, json, "value", token);
}
}
static int json_decode(lua_State *l)
{
json_parse_t json;
json_token_t token;
size_t json_len;
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
json.cfg = json_fetch_config(l);
json.data = luaL_checklstring(l, 1, &json_len);
json.current_depth = 0;
json.ptr = json.data;
/* Detect Unicode other than UTF-8 (see RFC 4627, Sec 3)
*
* CJSON can support any simple data type, hence only the first
* character is guaranteed to be ASCII (at worst: '"'). This is
* still enough to detect whether the wrong encoding is in use. */
if (json_len >= 2 && (!json.data[0] || !json.data[1]))
luaL_error(l, "JSON parser does not support UTF-16 or UTF-32");
/* Ensure the temporary buffer can hold the entire string.
* This means we no longer need to do length checks since the decoded
* string must be smaller than the entire json string */
json.tmp = strbuf_new(json_len);
if(json.tmp == NULL){
return luaL_error(l, "not enought memory");
}
json_next_token(&json, &token);
json_process_value(l, &json, &token);
/* Ensure there is no more input left */
json_next_token(&json, &token);
if (token.type != T_END)
json_throw_parse_error(l, &json, "the end", &token);
strbuf_free(json.tmp);
return 1;
}
/* ===== INITIALISATION ===== */
#if 0
#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
/* Compatibility for Lua 5.1.
*
* luaL_setfuncs() is used to create a module table where the functions have
* json_config_t as their first upvalue. Code borrowed from Lua 5.2 source. */
static void luaL_setfuncs (lua_State *l, const luaL_Reg *reg, int nup)
{
int i;
luaL_checkstack(l, nup, "too many upvalues");
for (; reg->name != NULL; reg++) { /* fill the table with given functions */
for (i = 0; i < nup; i++) /* copy upvalues to the top */
lua_pushvalue(l, -nup);
lua_pushcclosure(l, reg->func, nup); /* closure with those upvalues */
lua_setfield(l, -(nup + 2), reg->name);
}
lua_pop(l, nup); /* remove upvalues */
}
#endif
/* Call target function in protected mode with all supplied args.
* Assumes target function only returns a single non-nil value.
* Convert and return thrown errors as: nil, "error message" */
static int json_protect_conversion(lua_State *l)
{
int err;
/* Deliberately throw an error for invalid arguments */
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
/* pcall() the function stored as upvalue(1) */
lua_pushvalue(l, lua_upvalueindex(1));
lua_insert(l, 1);
err = lua_pcall(l, 1, 1, 0);
if (!err)
return 1;
if (err == LUA_ERRRUN) {
lua_pushnil(l);
lua_insert(l, -2);
return 2;
}
/* Since we are not using a custom error handler, the only remaining
* errors are memory related */
return luaL_error(l, "Memory allocation error in CJSON protected call");
}
#endif
// Module function map
#define MIN_OPT_LEVEL 2
#include "lrodefs.h"
const LUA_REG_TYPE cjson_map[] =
{
{ LSTRKEY( "encode" ), LFUNCVAL( json_encode ) },
{ LSTRKEY( "decode" ), LFUNCVAL( json_decode ) },
// { LSTRKEY( "encode_sparse_array" ), LFUNCVAL( json_cfg_encode_sparse_array ) },
// { LSTRKEY( "encode_max_depth" ), LFUNCVAL( json_cfg_encode_max_depth ) },
// { LSTRKEY( "decode_max_depth" ), LFUNCVAL( json_cfg_decode_max_depth ) },
// { LSTRKEY( "encode_number_precision" ), LFUNCVAL( json_cfg_encode_number_precision ) },
// { LSTRKEY( "encode_keep_buffer" ), LFUNCVAL( json_cfg_encode_keep_buffer ) },
// { LSTRKEY( "encode_invalid_numbers" ), LFUNCVAL( json_cfg_encode_invalid_numbers ) },
// { LSTRKEY( "decode_invalid_numbers" ), LFUNCVAL( json_cfg_decode_invalid_numbers ) },
// { LSTRKEY( "new" ), LFUNCVAL( lua_cjson_new ) },
#if LUA_OPTIMIZE_MEMORY > 0
#endif
{ LNILKEY, LNILVAL }
};
LUALIB_API int luaopen_cjson( lua_State *L )
{
/* Initialise number conversions */
// fpconv_init(); // not needed for a specific cpu.
if(-1==cfg_init(&_cfg)){
return luaL_error(L, "BUG: Unable to init config for cjson");;
}
#if LUA_OPTIMIZE_MEMORY > 0
return 0;
#else // #if LUA_OPTIMIZE_MEMORY > 0
luaL_register( L, AUXLIB_CJSON, cjson_map );
// Add constants
/* Set cjson.null */
lua_pushlightuserdata(l, NULL);
lua_setfield(l, -2, "null");
/* Return cjson table */
return 1;
#endif // #if LUA_OPTIMIZE_MEMORY > 0
}
#if 0
/* Return cjson module table */
static int lua_cjson_new(lua_State *l)
{
/* Initialise number conversions */
fpconv_init();
/* cjson module table */
lua_newtable(l);
/* Register functions with config data as upvalue */
json_create_config(l);
luaL_setfuncs(l, reg, 1);
/* Set cjson.null */
lua_pushlightuserdata(l, NULL);
lua_setfield(l, -2, "null");
/* Set module name / version fields */
lua_pushliteral(l, CJSON_MODNAME);
lua_setfield(l, -2, "_NAME");
lua_pushliteral(l, CJSON_VERSION);
lua_setfield(l, -2, "_VERSION");
return 1;
}
/* Return cjson.safe module table */
static int lua_cjson_safe_new(lua_State *l)
{
const char *func[] = { "decode", "encode", NULL };
int i;
lua_cjson_new(l);
/* Fix new() method */
lua_pushcfunction(l, lua_cjson_safe_new);
lua_setfield(l, -2, "new");
for (i = 0; func[i]; i++) {
lua_getfield(l, -1, func[i]);
lua_pushcclosure(l, json_protect_conversion, 1);
lua_setfield(l, -2, func[i]);
}
return 1;
}
int luaopen_cjson(lua_State *l)
{
lua_cjson_new(l);
#ifdef ENABLE_CJSON_GLOBAL
/* Register a global "cjson" table. */
lua_pushvalue(l, -1);
lua_setglobal(l, CJSON_MODNAME);
#endif
/* Return cjson table */
return 1;
}
int luaopen_cjson_safe(lua_State *l)
{
lua_cjson_safe_new(l);
/* Return cjson.safe table */
return 1;
}
#endif
/* vi:ai et sw=4 ts=4:
*/
...@@ -338,7 +338,7 @@ LUALIB_API int luaopen_file( lua_State *L ) ...@@ -338,7 +338,7 @@ LUALIB_API int luaopen_file( lua_State *L )
#if LUA_OPTIMIZE_MEMORY > 0 #if LUA_OPTIMIZE_MEMORY > 0
return 0; return 0;
#else // #if LUA_OPTIMIZE_MEMORY > 0 #else // #if LUA_OPTIMIZE_MEMORY > 0
luaL_register( L, AUXLIB_NODE, file_map ); luaL_register( L, AUXLIB_FILE, file_map );
// Add constants // Add constants
return 1; return 1;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment