-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjson.lua
More file actions
170 lines (158 loc) · 5.43 KB
/
Copy pathjson.lua
File metadata and controls
170 lines (158 loc) · 5.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
-- json.lua - Lightweight JSON library for Lua 5.1
-- Based on rxi/json.lua, compatible with Lua 5.1
local json = {}
-- Internal functions
local function kind_of(obj)
if type(obj) ~= 'table' then return type(obj) end
local i = 1
for _ in pairs(obj) do
if obj[i] ~= nil then i = i + 1 else return 'table' end
end
if i == 1 then return 'table' else return 'array' end
end
local function escape_str(s)
local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'}
local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'}
for i, c in ipairs(in_char) do
s = s:gsub(c, '\\' .. out_char[i])
end
return s
end
-- Returns pos, did_find
local function skip_delim(str, pos, delim, err_if_missing)
pos = pos + #str:match('^%s*', pos)
if str:sub(pos, pos) ~= delim then
if err_if_missing then
error("Expected '" .. delim .. "' near position " .. pos)
end
return pos, false
end
return pos + 1, true
end
-- Returns obj, pos
local function parse_str_val(str, pos, val)
val = val or ''
local early_end_error = 'End of input found while parsing string.'
if pos > #str then error(early_end_error) end
local c = str:sub(pos, pos)
if c == '"' then return val, pos + 1 end
if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end
-- We must have a \ character.
local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}
local nextc = str:sub(pos + 1, pos + 1)
if not nextc then error(early_end_error) end
return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc))
end
-- Returns obj, pos
local function parse_num_val(str, pos)
local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)
local val = tonumber(num_str)
if not val then error('Error parsing number at position ' .. pos .. '.') end
return val, pos + #num_str
end
-- Forward declaration
local parse
-- Returns obj, pos
local function parse_array_val(str, pos)
local obj, key = {}, 1
local found
pos, found = skip_delim(str, pos, ']', false)
if found then return obj, pos end
local val
repeat
val, pos = parse(str, pos)
obj[key] = val
key = key + 1
pos, found = skip_delim(str, pos, ',', false)
until not found
pos, found = skip_delim(str, pos, ']', true)
return obj, pos
end
-- Returns obj, pos
local function parse_obj_val(str, pos)
local obj = {}
local found
pos, found = skip_delim(str, pos, '}', false)
if found then return obj, pos end
local key
repeat
key, pos = parse(str, pos)
if type(key) ~= 'string' then
error('Expecting string key near position ' .. pos)
end
pos, found = skip_delim(str, pos, ':', true)
obj[key], pos = parse(str, pos)
pos, found = skip_delim(str, pos, ',', false)
until not found
pos, found = skip_delim(str, pos, '}', true)
return obj, pos
end
-- Returns obj, pos
parse = function(str, pos, end_delim)
pos = pos or 1
if pos > #str then error('Reached unexpected end of input.') end
local pos = pos + #str:match('^%s*', pos) -- Skip whitespace.
local first = str:sub(pos, pos)
if first == '{' then -- Parse object.
return parse_obj_val(str, pos + 1)
elseif first == '[' then -- Parse array.
return parse_array_val(str, pos + 1)
elseif first == '"' then -- Parse string.
return parse_str_val(str, pos + 1)
elseif first == '-' or first:match('%d') then -- Parse number.
return parse_num_val(str, pos)
elseif first == end_delim then -- End of an object or the array.
return nil, pos + 1
else -- Parse true, false, or null.
local literals = {['true'] = true, ['false'] = false, ['null'] = nil}
for lit_str, lit_val in pairs(literals) do
local lit_end = pos + #lit_str - 1
if str:sub(pos, lit_end) == lit_str then
return lit_val, lit_end + 1
end
end
local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)
error('Invalid json syntax starting at ' .. pos_info_str)
end
end
-- Public interface
function json.encode(obj, as_key)
local type = type(obj)
if type == 'string' then
return '"' .. escape_str(obj) .. '"'
elseif type == 'number' then
return tostring(obj)
elseif type == 'table' then
local result_str = {}
local obj_type = kind_of(obj)
if obj_type == 'array' then
for i, val in ipairs(obj) do
table.insert(result_str, json.encode(val))
end
return '[' .. table.concat(result_str, ',') .. ']'
else -- obj_type is 'table'.
for key, val in pairs(obj) do
table.insert(result_str, json.encode(key, true) .. ':' .. json.encode(val))
end
return '{' .. table.concat(result_str, ',') .. '}'
end
elseif type == 'boolean' then
return obj and 'true' or 'false'
elseif type == 'nil' then
return 'null'
else
error('Encoding a ' .. type .. ' is not supported')
end
end
function json.decode(s)
if type(s) ~= 'string' then
error('Expected string, got ' .. type(s))
end
local obj, pos = parse(s, 1)
local pos = pos + #s:match('^%s*', pos) -- Skip trailing whitespace.
if pos <= #s then
error('Trailing garbage after JSON at position ' .. pos)
end
return obj
end
return json