-
Notifications
You must be signed in to change notification settings - Fork 0
/
json_parser.lua
202 lines (194 loc) · 4.61 KB
/
json_parser.lua
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
json = {}
json.null = {}
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
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
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
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
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
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
function json.stringify(obj, as_key)
local s = {}
local kind = kind_of(obj)
if kind == "array" then
if as_key then
error("Can't encode array as key.")
end
s[#s + 1] = "["
for i, val in ipairs(obj) do
if i > 1 then
s[#s + 1] = ", "
end
s[#s + 1] = json.stringify(val)
end
s[#s + 1] = "]"
elseif kind == "table" then
if as_key then
error("Can't encode table as key.")
end
s[#s + 1] = "{"
for k, v in pairs(obj) do
if #s > 1 then
s[#s + 1] = ", "
end
s[#s + 1] = json.stringify(k, true)
s[#s + 1] = ":"
s[#s + 1] = json.stringify(v)
end
s[#s + 1] = "}"
elseif kind == "string" then
return '"' .. escape_str(obj) .. '"'
elseif kind == "number" then
if as_key then
return '"' .. tostring(obj) .. '"'
end
return tostring(obj)
elseif kind == "boolean" then
return tostring(obj)
elseif kind == "nil" then
return "null"
else
error("Unjsonifiable type: " .. kind .. ".")
end
return table.concat(s)
end
function json.parse(str, pos, end_delim)
pos = pos or 1
if pos > #str then
error("Reached unexpected end of input.")
end
pos = pos + #str:match("^%s*", pos)
local first = str:sub(pos, pos)
if first == "{" then
local obj, key, delim_found = {}, true, true
pos = pos + 1
while true do
key, pos = json.parse(str, pos, "}")
if key == nil then
return obj, pos
end
if not delim_found then
error("Comma missing between object items.")
end
pos = skip_delim(str, pos, ":", true)
obj[key], pos = json.parse(str, pos)
pos, delim_found = skip_delim(str, pos, ",")
end
elseif first == "[" then
local arr, val, delim_found = {}, true, true
pos = pos + 1
while true do
val, pos = json.parse(str, pos, "]")
if val == nil then
return arr, pos
end
if not delim_found then
error("Comma missing between array items.")
end
arr[#arr + 1] = val
pos, delim_found = skip_delim(str, pos, ",")
end
elseif first == '"' then
return parse_str_val(str, pos + 1)
elseif first == "-" or first:match("%d") then
return parse_num_val(str, pos)
elseif first == end_delim then
return nil, pos + 1
else
local literals = {["true"] = true, ["false"] = false, ["null"] = json.null}
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
function dump(o)
if type(o) == "table" then
local s = "{ "
local first_key = true
for k, v in pairs(o) do
if not first_key then
s = s .. ", "
end
first_key = false
if type(k) == "number" then
s = s .. "[" .. k .. "]"
elseif type(k) == "string" and k:match("^[%a_][%w_]*$") and k ~= "function" then
s = s .. k
else
s = s .. '["' .. k .. '"]'
end
s = s .. " = " .. dump(v)
end
return s .. " }"
elseif type(o) == "string" then
if string.find(o, '"') then
return "'" .. o .. "'"
else
return '"' .. o .. '"'
end
else
return tostring(o)
end
end