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