1+ #pragma once
2+ #include " pch.h"
3+
4+ // -- [[ JSON.lua
5+ // A compact pure - Lua JSON library.
6+ // The main functions are : JSON.stringify, JSON.parse.
7+ // ## JSON.stringify:
8+ // This expects the following to be true of any tables being encoded :
9+ // *They only have string or number keys.Number keys must be represented as
10+ // strings in JSON; this is part of the JSON spec.
11+ // * They are not recursive.Such a structure cannot be specified in JSON.
12+ // A Lua table is considered to be an array ifand only if its set of keys is a
13+ // consecutive sequence of positive integers starting at 1. Arrays are encoded like
14+ // so : `[2, 3, false, "hi"]`. Any other type of Lua table is encoded as a JSON
15+ // object, encoded like so : `{"key1": 2, "key2" : false}`.
16+ // Because the Lua nil value cannot be a key, and as a table value is considerd
17+ // equivalent to a missing key, there is no way to express the JSON "null" value in
18+ // a Lua table.The only way this will output "null" is if your entire input obj is
19+ // nil itself.
20+ // An empty Lua table, {}, could be considered either a JSON object or array -
21+ // it's an ambiguous edge case. We choose to treat this as an object as it is the
22+ // more general type.
23+ // To be clear, none of the above considerations is a limitation of this code.
24+ // Rather, it is what we get when we completely observe the JSON specification for
25+ // as arbitrary a Lua object as JSON is capable of expressing.
26+ // ## JSON.parse:
27+ // This function parses JSON, with the exception that it does not pay attention to
28+ // \u - escaped unicode code points in strings.
29+ // It is difficult for Lua to return null as a value.In order to prevent the loss
30+ // of keys with a null value in a JSON string, this function uses the one - off
31+ // table value JSON.null(which is just an empty table) to indicate null values.
32+ // This way you can check if a value is null with the conditional
33+ // `val == JSON.null`.
34+ // If you have control over the data and are using Lua, I would recommend just
35+ // avoiding null values in your data to begin with.
36+ // --]]
37+
38+ static void LoadScriptModule_JSON (sol::state* L)
39+ {
40+ L->script (R"(
41+ JSON = {}
42+
43+ -- Internal functions.
44+
45+ local function kind_of(obj)
46+ if type(obj) ~= 'table' then return type(obj) end
47+ local i = 1
48+ for _ in pairs(obj) do
49+ if obj[i] ~= nil then i = i + 1 else return 'table' end
50+ end
51+ if i == 1 then return 'table' else return 'array' end
52+ end
53+
54+ local function escape_str(s)
55+ local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'}
56+ local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'}
57+ for i, c in ipairs(in_char) do
58+ s = s:gsub(c, '\\' .. out_char[i])
59+ end
60+ return s
61+ end
62+
63+ -- Returns pos, did_find; there are two cases:
64+ -- 1. Delimiter found: pos = pos after leading space + delim; did_find = true.
65+ -- 2. Delimiter not found: pos = pos after leading space; did_find = false.
66+ -- This throws an error if err_if_missing is true and the delim is not found.
67+ local function skip_delim(str, pos, delim, err_if_missing)
68+ pos = pos + #str:match('^%s*', pos)
69+ if str:sub(pos, pos) ~= delim then
70+ if err_if_missing then
71+ error('Expected ' .. delim .. ' near position ' .. pos)
72+ end
73+ return pos, false
74+ end
75+ return pos + 1, true
76+ end
77+
78+ -- Expects the given pos to be the first character after the opening quote.
79+ -- Returns val, pos; the returned pos is after the closing quote character.
80+ local function parse_str_val(str, pos, val)
81+ val = val or ''
82+ local early_end_error = 'End of input found while parsing string.'
83+ if pos > #str then error(early_end_error) end
84+ local c = str:sub(pos, pos)
85+ if c == '"' then return val, pos + 1 end
86+ if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end
87+ -- We must have a \ character.
88+ local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}
89+ local nextc = str:sub(pos + 1, pos + 1)
90+ if not nextc then error(early_end_error) end
91+ return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc))
92+ end
93+
94+ -- Returns val, pos; the returned pos is after the number's final character.
95+ local function parse_num_val(str, pos)
96+ local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)
97+ local val = tonumber(num_str)
98+ if not val then error('Error parsing number at position ' .. pos .. '.') end
99+ return val, pos + #num_str
100+ end
101+
102+
103+ -- Public values and functions.
104+
105+ function JSON.stringify(obj, as_key)
106+ local s = {} -- We'll build the string as an array of strings to be concatenated.
107+ local kind = kind_of(obj) -- This is 'array' if it's an array or type(obj) otherwise.
108+ if kind == 'array' then
109+ if as_key then error('Can\'t encode array as key.') end
110+ s[#s + 1] = '['
111+ for i, val in ipairs(obj) do
112+ if i > 1 then s[#s + 1] = ', ' end
113+ s[#s + 1] = JSON.stringify(val)
114+ end
115+ s[#s + 1] = ']'
116+ elseif kind == 'table' then
117+ if as_key then error('Can\'t encode table as key.') end
118+ s[#s + 1] = '{'
119+ for k, v in pairs(obj) do
120+ if #s > 1 then s[#s + 1] = ', ' end
121+ s[#s + 1] = JSON.stringify(k, true)
122+ s[#s + 1] = ':'
123+ s[#s + 1] = JSON.stringify(v)
124+ end
125+ s[#s + 1] = '}'
126+ elseif kind == 'string' then
127+ return '"' .. escape_str(obj) .. '"'
128+ elseif kind == 'number' then
129+ if as_key then return '"' .. tostring(obj) .. '"' end
130+ return tostring(obj)
131+ elseif kind == 'boolean' then
132+ return tostring(obj)
133+ elseif kind == 'nil' then
134+ return 'null'
135+ else
136+ error('Unjsonifiable type: ' .. kind .. '.')
137+ end
138+ return table.concat(s)
139+ end
140+
141+ JSON.null = {} -- This is a one-off table to represent the null value.
142+
143+ function JSON.parse(str, pos, end_delim)
144+ pos = pos or 1
145+ if pos > #str then error('Reached unexpected end of input.') end
146+ local pos = pos + #str:match('^%s*', pos) -- Skip whitespace.
147+ local first = str:sub(pos, pos)
148+ if first == '{' then -- Parse an object.
149+ local obj, key, delim_found = {}, true, true
150+ pos = pos + 1
151+ while true do
152+ key, pos = JSON.parse(str, pos, '}')
153+ if key == nil then return obj, pos end
154+ if not delim_found then error('Comma missing between object items.') end
155+ pos = skip_delim(str, pos, ':', true) -- true -> error if missing.
156+ obj[key], pos = JSON.parse(str, pos)
157+ pos, delim_found = skip_delim(str, pos, ',')
158+ end
159+ elseif first == '[' then -- Parse an array.
160+ local arr, val, delim_found = {}, true, true
161+ pos = pos + 1
162+ while true do
163+ val, pos = JSON.parse(str, pos, ']')
164+ if val == nil then return arr, pos end
165+ if not delim_found then error('Comma missing between array items.') end
166+ arr[#arr + 1] = val
167+ pos, delim_found = skip_delim(str, pos, ',')
168+ end
169+ elseif first == '"' then -- Parse a string.
170+ return parse_str_val(str, pos + 1)
171+ elseif first == '-' or first:match('%d') then -- Parse a number.
172+ return parse_num_val(str, pos)
173+ elseif first == end_delim then -- End of an object or array.
174+ return nil, pos + 1
175+ else -- Parse true, false, or null.
176+ local literals = {['true'] = true, ['false'] = false, ['null'] = JSON.null}
177+ for lit_str, lit_val in pairs(literals) do
178+ local lit_end = pos + #lit_str - 1
179+ if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end
180+ end
181+ local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)
182+ error('Invalid JSON syntax starting at ' .. pos_info_str)
183+ end
184+ end
185+ )" );
186+ }
0 commit comments