diff options
author | Mike Crute <mike@crute.us> | 2022-09-28 18:37:03 -0700 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2022-09-28 18:37:03 -0700 |
commit | 055d93af8a33ade8b40784e098d69964e74a75b5 (patch) | |
tree | 8c23d8f221a9e81d231b5030c24be0e6520d470e | |
parent | 923447cf8039a57f7cc086c7821465bca2ef85d7 (diff) | |
download | dotfiles-055d93af8a33ade8b40784e098d69964e74a75b5.tar.bz2 dotfiles-055d93af8a33ade8b40784e098d69964e74a75b5.tar.xz dotfiles-055d93af8a33ade8b40784e098d69964e74a75b5.zip |
Add json.lua for a future project
-rw-r--r-- | .config/awesome/json.lua | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/.config/awesome/json.lua b/.config/awesome/json.lua new file mode 100644 index 0000000..90b368a --- /dev/null +++ b/.config/awesome/json.lua | |||
@@ -0,0 +1,388 @@ | |||
1 | -- | ||
2 | -- json.lua (https://github.com/rxi/json.lua) | ||
3 | -- | ||
4 | -- Copyright (c) 2020 rxi | ||
5 | -- | ||
6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
7 | -- this software and associated documentation files (the "Software"), to deal in | ||
8 | -- the Software without restriction, including without limitation the rights to | ||
9 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
10 | -- of the Software, and to permit persons to whom the Software is furnished to do | ||
11 | -- so, subject to the following conditions: | ||
12 | -- | ||
13 | -- The above copyright notice and this permission notice shall be included in all | ||
14 | -- copies or substantial portions of the Software. | ||
15 | -- | ||
16 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
17 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
18 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
19 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
20 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
21 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
22 | -- SOFTWARE. | ||
23 | -- | ||
24 | |||
25 | local json = { _version = "0.1.2" } | ||
26 | |||
27 | ------------------------------------------------------------------------------- | ||
28 | -- Encode | ||
29 | ------------------------------------------------------------------------------- | ||
30 | |||
31 | local encode | ||
32 | |||
33 | local escape_char_map = { | ||
34 | [ "\\" ] = "\\", | ||
35 | [ "\"" ] = "\"", | ||
36 | [ "\b" ] = "b", | ||
37 | [ "\f" ] = "f", | ||
38 | [ "\n" ] = "n", | ||
39 | [ "\r" ] = "r", | ||
40 | [ "\t" ] = "t", | ||
41 | } | ||
42 | |||
43 | local escape_char_map_inv = { [ "/" ] = "/" } | ||
44 | for k, v in pairs(escape_char_map) do | ||
45 | escape_char_map_inv[v] = k | ||
46 | end | ||
47 | |||
48 | |||
49 | local function escape_char(c) | ||
50 | return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) | ||
51 | end | ||
52 | |||
53 | |||
54 | local function encode_nil(val) | ||
55 | return "null" | ||
56 | end | ||
57 | |||
58 | |||
59 | local function encode_table(val, stack) | ||
60 | local res = {} | ||
61 | stack = stack or {} | ||
62 | |||
63 | -- Circular reference? | ||
64 | if stack[val] then error("circular reference") end | ||
65 | |||
66 | stack[val] = true | ||
67 | |||
68 | if rawget(val, 1) ~= nil or next(val) == nil then | ||
69 | -- Treat as array -- check keys are valid and it is not sparse | ||
70 | local n = 0 | ||
71 | for k in pairs(val) do | ||
72 | if type(k) ~= "number" then | ||
73 | error("invalid table: mixed or invalid key types") | ||
74 | end | ||
75 | n = n + 1 | ||
76 | end | ||
77 | if n ~= #val then | ||
78 | error("invalid table: sparse array") | ||
79 | end | ||
80 | -- Encode | ||
81 | for i, v in ipairs(val) do | ||
82 | table.insert(res, encode(v, stack)) | ||
83 | end | ||
84 | stack[val] = nil | ||
85 | return "[" .. table.concat(res, ",") .. "]" | ||
86 | |||
87 | else | ||
88 | -- Treat as an object | ||
89 | for k, v in pairs(val) do | ||
90 | if type(k) ~= "string" then | ||
91 | error("invalid table: mixed or invalid key types") | ||
92 | end | ||
93 | table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) | ||
94 | end | ||
95 | stack[val] = nil | ||
96 | return "{" .. table.concat(res, ",") .. "}" | ||
97 | end | ||
98 | end | ||
99 | |||
100 | |||
101 | local function encode_string(val) | ||
102 | return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' | ||
103 | end | ||
104 | |||
105 | |||
106 | local function encode_number(val) | ||
107 | -- Check for NaN, -inf and inf | ||
108 | if val ~= val or val <= -math.huge or val >= math.huge then | ||
109 | error("unexpected number value '" .. tostring(val) .. "'") | ||
110 | end | ||
111 | return string.format("%.14g", val) | ||
112 | end | ||
113 | |||
114 | |||
115 | local type_func_map = { | ||
116 | [ "nil" ] = encode_nil, | ||
117 | [ "table" ] = encode_table, | ||
118 | [ "string" ] = encode_string, | ||
119 | [ "number" ] = encode_number, | ||
120 | [ "boolean" ] = tostring, | ||
121 | } | ||
122 | |||
123 | |||
124 | encode = function(val, stack) | ||
125 | local t = type(val) | ||
126 | local f = type_func_map[t] | ||
127 | if f then | ||
128 | return f(val, stack) | ||
129 | end | ||
130 | error("unexpected type '" .. t .. "'") | ||
131 | end | ||
132 | |||
133 | |||
134 | function json.encode(val) | ||
135 | return ( encode(val) ) | ||
136 | end | ||
137 | |||
138 | |||
139 | ------------------------------------------------------------------------------- | ||
140 | -- Decode | ||
141 | ------------------------------------------------------------------------------- | ||
142 | |||
143 | local parse | ||
144 | |||
145 | local function create_set(...) | ||
146 | local res = {} | ||
147 | for i = 1, select("#", ...) do | ||
148 | res[ select(i, ...) ] = true | ||
149 | end | ||
150 | return res | ||
151 | end | ||
152 | |||
153 | local space_chars = create_set(" ", "\t", "\r", "\n") | ||
154 | local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") | ||
155 | local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") | ||
156 | local literals = create_set("true", "false", "null") | ||
157 | |||
158 | local literal_map = { | ||
159 | [ "true" ] = true, | ||
160 | [ "false" ] = false, | ||
161 | [ "null" ] = nil, | ||
162 | } | ||
163 | |||
164 | |||
165 | local function next_char(str, idx, set, negate) | ||
166 | for i = idx, #str do | ||
167 | if set[str:sub(i, i)] ~= negate then | ||
168 | return i | ||
169 | end | ||
170 | end | ||
171 | return #str + 1 | ||
172 | end | ||
173 | |||
174 | |||
175 | local function decode_error(str, idx, msg) | ||
176 | local line_count = 1 | ||
177 | local col_count = 1 | ||
178 | for i = 1, idx - 1 do | ||
179 | col_count = col_count + 1 | ||
180 | if str:sub(i, i) == "\n" then | ||
181 | line_count = line_count + 1 | ||
182 | col_count = 1 | ||
183 | end | ||
184 | end | ||
185 | error( string.format("%s at line %d col %d", msg, line_count, col_count) ) | ||
186 | end | ||
187 | |||
188 | |||
189 | local function codepoint_to_utf8(n) | ||
190 | -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa | ||
191 | local f = math.floor | ||
192 | if n <= 0x7f then | ||
193 | return string.char(n) | ||
194 | elseif n <= 0x7ff then | ||
195 | return string.char(f(n / 64) + 192, n % 64 + 128) | ||
196 | elseif n <= 0xffff then | ||
197 | return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) | ||
198 | elseif n <= 0x10ffff then | ||
199 | return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, | ||
200 | f(n % 4096 / 64) + 128, n % 64 + 128) | ||
201 | end | ||
202 | error( string.format("invalid unicode codepoint '%x'", n) ) | ||
203 | end | ||
204 | |||
205 | |||
206 | local function parse_unicode_escape(s) | ||
207 | local n1 = tonumber( s:sub(1, 4), 16 ) | ||
208 | local n2 = tonumber( s:sub(7, 10), 16 ) | ||
209 | -- Surrogate pair? | ||
210 | if n2 then | ||
211 | return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) | ||
212 | else | ||
213 | return codepoint_to_utf8(n1) | ||
214 | end | ||
215 | end | ||
216 | |||
217 | |||
218 | local function parse_string(str, i) | ||
219 | local res = "" | ||
220 | local j = i + 1 | ||
221 | local k = j | ||
222 | |||
223 | while j <= #str do | ||
224 | local x = str:byte(j) | ||
225 | |||
226 | if x < 32 then | ||
227 | decode_error(str, j, "control character in string") | ||
228 | |||
229 | elseif x == 92 then -- `\`: Escape | ||
230 | res = res .. str:sub(k, j - 1) | ||
231 | j = j + 1 | ||
232 | local c = str:sub(j, j) | ||
233 | if c == "u" then | ||
234 | local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) | ||
235 | or str:match("^%x%x%x%x", j + 1) | ||
236 | or decode_error(str, j - 1, "invalid unicode escape in string") | ||
237 | res = res .. parse_unicode_escape(hex) | ||
238 | j = j + #hex | ||
239 | else | ||
240 | if not escape_chars[c] then | ||
241 | decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") | ||
242 | end | ||
243 | res = res .. escape_char_map_inv[c] | ||
244 | end | ||
245 | k = j + 1 | ||
246 | |||
247 | elseif x == 34 then -- `"`: End of string | ||
248 | res = res .. str:sub(k, j - 1) | ||
249 | return res, j + 1 | ||
250 | end | ||
251 | |||
252 | j = j + 1 | ||
253 | end | ||
254 | |||
255 | decode_error(str, i, "expected closing quote for string") | ||
256 | end | ||
257 | |||
258 | |||
259 | local function parse_number(str, i) | ||
260 | local x = next_char(str, i, delim_chars) | ||
261 | local s = str:sub(i, x - 1) | ||
262 | local n = tonumber(s) | ||
263 | if not n then | ||
264 | decode_error(str, i, "invalid number '" .. s .. "'") | ||
265 | end | ||
266 | return n, x | ||
267 | end | ||
268 | |||
269 | |||
270 | local function parse_literal(str, i) | ||
271 | local x = next_char(str, i, delim_chars) | ||
272 | local word = str:sub(i, x - 1) | ||
273 | if not literals[word] then | ||
274 | decode_error(str, i, "invalid literal '" .. word .. "'") | ||
275 | end | ||
276 | return literal_map[word], x | ||
277 | end | ||
278 | |||
279 | |||
280 | local function parse_array(str, i) | ||
281 | local res = {} | ||
282 | local n = 1 | ||
283 | i = i + 1 | ||
284 | while 1 do | ||
285 | local x | ||
286 | i = next_char(str, i, space_chars, true) | ||
287 | -- Empty / end of array? | ||
288 | if str:sub(i, i) == "]" then | ||
289 | i = i + 1 | ||
290 | break | ||
291 | end | ||
292 | -- Read token | ||
293 | x, i = parse(str, i) | ||
294 | res[n] = x | ||
295 | n = n + 1 | ||
296 | -- Next token | ||
297 | i = next_char(str, i, space_chars, true) | ||
298 | local chr = str:sub(i, i) | ||
299 | i = i + 1 | ||
300 | if chr == "]" then break end | ||
301 | if chr ~= "," then decode_error(str, i, "expected ']' or ','") end | ||
302 | end | ||
303 | return res, i | ||
304 | end | ||
305 | |||
306 | |||
307 | local function parse_object(str, i) | ||
308 | local res = {} | ||
309 | i = i + 1 | ||
310 | while 1 do | ||
311 | local key, val | ||
312 | i = next_char(str, i, space_chars, true) | ||
313 | -- Empty / end of object? | ||
314 | if str:sub(i, i) == "}" then | ||
315 | i = i + 1 | ||
316 | break | ||
317 | end | ||
318 | -- Read key | ||
319 | if str:sub(i, i) ~= '"' then | ||
320 | decode_error(str, i, "expected string for key") | ||
321 | end | ||
322 | key, i = parse(str, i) | ||
323 | -- Read ':' delimiter | ||
324 | i = next_char(str, i, space_chars, true) | ||
325 | if str:sub(i, i) ~= ":" then | ||
326 | decode_error(str, i, "expected ':' after key") | ||
327 | end | ||
328 | i = next_char(str, i + 1, space_chars, true) | ||
329 | -- Read value | ||
330 | val, i = parse(str, i) | ||
331 | -- Set | ||
332 | res[key] = val | ||
333 | -- Next token | ||
334 | i = next_char(str, i, space_chars, true) | ||
335 | local chr = str:sub(i, i) | ||
336 | i = i + 1 | ||
337 | if chr == "}" then break end | ||
338 | if chr ~= "," then decode_error(str, i, "expected '}' or ','") end | ||
339 | end | ||
340 | return res, i | ||
341 | end | ||
342 | |||
343 | |||
344 | local char_func_map = { | ||
345 | [ '"' ] = parse_string, | ||
346 | [ "0" ] = parse_number, | ||
347 | [ "1" ] = parse_number, | ||
348 | [ "2" ] = parse_number, | ||
349 | [ "3" ] = parse_number, | ||
350 | [ "4" ] = parse_number, | ||
351 | [ "5" ] = parse_number, | ||
352 | [ "6" ] = parse_number, | ||
353 | [ "7" ] = parse_number, | ||
354 | [ "8" ] = parse_number, | ||
355 | [ "9" ] = parse_number, | ||
356 | [ "-" ] = parse_number, | ||
357 | [ "t" ] = parse_literal, | ||
358 | [ "f" ] = parse_literal, | ||
359 | [ "n" ] = parse_literal, | ||
360 | [ "[" ] = parse_array, | ||
361 | [ "{" ] = parse_object, | ||
362 | } | ||
363 | |||
364 | |||
365 | parse = function(str, idx) | ||
366 | local chr = str:sub(idx, idx) | ||
367 | local f = char_func_map[chr] | ||
368 | if f then | ||
369 | return f(str, idx) | ||
370 | end | ||
371 | decode_error(str, idx, "unexpected character '" .. chr .. "'") | ||
372 | end | ||
373 | |||
374 | |||
375 | function json.decode(str) | ||
376 | if type(str) ~= "string" then | ||
377 | error("expected argument of type string, got " .. type(str)) | ||
378 | end | ||
379 | local res, idx = parse(str, next_char(str, 1, space_chars, true)) | ||
380 | idx = next_char(str, idx, space_chars, true) | ||
381 | if idx <= #str then | ||
382 | decode_error(str, idx, "trailing garbage") | ||
383 | end | ||
384 | return res | ||
385 | end | ||
386 | |||
387 | |||
388 | return json | ||