util.pluginloader: Return full file path from internal file loader on success, not...
[prosody.git] / util / json.lua
1
2 local type = type;
3 local t_insert, t_concat, t_remove = table.insert, table.concat, table.remove;
4 local s_char = string.char;
5 local tostring, tonumber = tostring, tonumber;
6 local pairs, ipairs = pairs, ipairs;
7 local next = next;
8 local error = error;
9 local newproxy, getmetatable = newproxy, getmetatable;
10 local print = print;
11
12 --module("json")
13 local json = {};
14
15 local null = newproxy and newproxy(true) or {};
16 if getmetatable and getmetatable(null) then
17         getmetatable(null).__tostring = function() return "null"; end;
18 end
19 json.null = null;
20
21 local escapes = {
22         ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b",
23         ["\f"] = "\\f", ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"};
24 local unescapes = {
25         ["\""] = "\"", ["\\"] = "\\", ["/"] = "/",
26         b = "\b", f = "\f", n = "\n", r = "\r", t = "\t"};
27 for i=0,31 do
28         local ch = s_char(i);
29         if not escapes[ch] then escapes[ch] = ("\\u%.4X"):format(i); end
30 end
31
32 local valid_types = {
33         number  = true,
34         string  = true,
35         table   = true,
36         boolean = true
37 };
38 local special_keys = {
39         __array = true;
40         __hash  = true;
41 };
42
43 local simplesave, tablesave, arraysave, stringsave;
44
45 function stringsave(o, buffer)
46         -- FIXME do proper utf-8 and binary data detection
47         t_insert(buffer, "\""..(o:gsub(".", escapes)).."\"");
48 end
49
50 function arraysave(o, buffer)
51         t_insert(buffer, "[");
52         if next(o) then
53                 for i,v in ipairs(o) do
54                         simplesave(v, buffer);
55                         t_insert(buffer, ",");
56                 end
57                 t_remove(buffer);
58         end
59         t_insert(buffer, "]");
60 end
61
62 function tablesave(o, buffer)
63         local __array = {};
64         local __hash = {};
65         local hash = {};
66         for i,v in ipairs(o) do
67                 __array[i] = v;
68         end
69         for k,v in pairs(o) do
70                 local ktype, vtype = type(k), type(v);
71                 if valid_types[vtype] or v == null then
72                         if ktype == "string" and not special_keys[k] then
73                                 hash[k] = v;
74                         elseif (valid_types[ktype] or k == null) and __array[k] == nil then
75                                 __hash[k] = v;
76                         end
77                 end
78         end
79         if next(__hash) ~= nil or next(hash) ~= nil or next(__array) == nil then
80                 t_insert(buffer, "{");
81                 local mark = #buffer;
82                 for k,v in pairs(hash) do
83                         stringsave(k, buffer);
84                         t_insert(buffer, ":");
85                         simplesave(v, buffer);
86                         t_insert(buffer, ",");
87                 end
88                 if next(__hash) ~= nil then
89                         t_insert(buffer, "\"__hash\":[");
90                         for k,v in pairs(__hash) do
91                                 simplesave(k, buffer);
92                                 t_insert(buffer, ",");
93                                 simplesave(v, buffer);
94                                 t_insert(buffer, ",");
95                         end
96                         t_remove(buffer);
97                         t_insert(buffer, "]");
98                         t_insert(buffer, ",");
99                 end
100                 if next(__array) then
101                         t_insert(buffer, "\"__array\":");
102                         arraysave(__array, buffer);
103                         t_insert(buffer, ",");
104                 end
105                 if mark ~= #buffer then t_remove(buffer); end
106                 t_insert(buffer, "}");
107         else
108                 arraysave(__array, buffer);
109         end
110 end
111
112 function simplesave(o, buffer)
113         local t = type(o);
114         if t == "number" then
115                 t_insert(buffer, tostring(o));
116         elseif t == "string" then
117                 stringsave(o, buffer);
118         elseif t == "table" then
119                 tablesave(o, buffer);
120         elseif t == "boolean" then
121                 t_insert(buffer, (o and "true" or "false"));
122         else
123                 t_insert(buffer, "null");
124         end
125 end
126
127 function json.encode(obj)
128         local t = {};
129         simplesave(obj, t);
130         return t_concat(t);
131 end
132
133 -----------------------------------
134
135
136 function json.decode(json)
137         local pos = 1;
138         local current = {};
139         local stack = {};
140         local ch, peek;
141         local function next()
142                 ch = json:sub(pos, pos);
143                 pos = pos+1;
144                 peek = json:sub(pos, pos);
145                 return ch;
146         end
147         
148         local function skipwhitespace()
149                 while ch and (ch == "\r" or ch == "\n" or ch == "\t" or ch == " ") do
150                         next();
151                 end
152         end
153         local function skiplinecomment()
154                 repeat next(); until not(ch) or ch == "\r" or ch == "\n";
155                 skipwhitespace();
156         end
157         local function skipstarcomment()
158                 next(); next(); -- skip '/', '*'
159                 while peek and ch ~= "*" and peek ~= "/" do next(); end
160                 if not peek then error("eof in star comment") end
161                 next(); next(); -- skip '*', '/'
162                 skipwhitespace();
163         end
164         local function skipstuff()
165                 while true do
166                         skipwhitespace();
167                         if ch == "/" and peek == "*" then
168                                 skipstarcomment();
169                         elseif ch == "/" and peek == "*" then
170                                 skiplinecomment();
171                         else
172                                 return;
173                         end
174                 end
175         end
176         
177         local readvalue;
178         local function readarray()
179                 local t = {};
180                 next(); -- skip '['
181                 skipstuff();
182                 if ch == "]" then next(); return t; end
183                 t_insert(t, readvalue());
184                 while true do
185                         skipstuff();
186                         if ch == "]" then next(); return t; end
187                         if not ch then error("eof while reading array");
188                         elseif ch == "," then next();
189                         elseif ch then error("unexpected character in array, comma expected"); end
190                         if not ch then error("eof while reading array"); end
191                         t_insert(t, readvalue());
192                 end
193         end
194         
195         local function checkandskip(c)
196                 local x = ch or "eof";
197                 if x ~= c then error("unexpected "..x..", '"..c.."' expected"); end
198                 next();
199         end
200         local function readliteral(lit, val)
201                 for c in lit:gmatch(".") do
202                         checkandskip(c);
203                 end
204                 return val;
205         end
206         local function readstring()
207                 local s = "";
208                 checkandskip("\"");
209                 while ch do
210                         while ch and ch ~= "\\" and ch ~= "\"" do
211                                 s = s..ch; next();
212                         end
213                         if ch == "\\" then
214                                 next();
215                                 if unescapes[ch] then
216                                         s = s..unescapes[ch];
217                                         next();
218                                 elseif ch == "u" then
219                                         local seq = "";
220                                         for i=1,4 do
221                                                 next();
222                                                 if not ch then error("unexpected eof in string"); end
223                                                 if not ch:match("[0-9a-fA-F]") then error("invalid unicode escape sequence in string"); end
224                                                 seq = seq..ch;
225                                         end
226                                         s = s..s.char(tonumber(seq, 16)); -- FIXME do proper utf-8
227                                         next();
228                                 else error("invalid escape sequence in string"); end
229                         end
230                         if ch == "\"" then
231                                 next();
232                                 return s;
233                         end
234                 end
235                 error("eof while reading string");
236         end
237         local function readnumber()
238                 local s = "";
239                 if ch == "-" then
240                         s = s..ch; next();
241                         if not ch:match("[0-9]") then error("number format error"); end
242                 end
243                 if ch == "0" then
244                         s = s..ch; next();
245                         if ch:match("[0-9]") then error("number format error"); end
246                 else
247                         while ch and ch:match("[0-9]") do
248                                 s = s..ch; next();
249                         end
250                 end
251                 if ch == "." then
252                         s = s..ch; next();
253                         if not ch:match("[0-9]") then error("number format error"); end
254                         while ch and ch:match("[0-9]") do
255                                 s = s..ch; next();
256                         end
257                         if ch == "e" or ch == "E" then
258                                 s = s..ch; next();
259                                 if ch == "+" or ch == "-" then
260                                         s = s..ch; next();
261                                         if not ch:match("[0-9]") then error("number format error"); end
262                                         while ch and ch:match("[0-9]") do
263                                                 s = s..ch; next();
264                                         end
265                                 end
266                         end
267                 end
268                 return tonumber(s);
269         end
270         local function readmember(t)
271                 skipstuff();
272                 local k = readstring();
273                 skipstuff();
274                 checkandskip(":");
275                 t[k] = readvalue();
276         end
277         local function fixobject(obj)
278                 local __array = obj.__array;
279                 if __array then
280                         obj.__array = nil;
281                         for i,v in ipairs(__array) do
282                                 t_insert(obj, v);
283                         end
284                 end
285                 local __hash = obj.__hash;
286                 if __hash then
287                         obj.__hash = nil;
288                         local k;
289                         for i,v in ipairs(__hash) do
290                                 if k ~= nil then
291                                         obj[k] = v; k = nil;
292                                 else
293                                         k = v;
294                                 end
295                         end
296                 end
297                 return obj;
298         end
299         local function readobject()
300                 local t = {};
301                 next(); -- skip '{'
302                 skipstuff();
303                 if ch == "}" then next(); return t; end
304                 if not ch then error("eof while reading object"); end
305                 readmember(t);
306                 while true do
307                         skipstuff();
308                         if ch == "}" then next(); return fixobject(t); end
309                         if not ch then error("eof while reading object");
310                         elseif ch == "," then next();
311                         elseif ch then error("unexpected character in object, comma expected"); end
312                         if not ch then error("eof while reading object"); end
313                         readmember(t);
314                 end
315         end
316         
317         function readvalue()
318                 skipstuff();
319                 while ch do
320                         if ch == "{" then
321                                 return readobject();
322                         elseif ch == "[" then
323                                 return readarray();
324                         elseif ch == "\"" then
325                                 return readstring();
326                         elseif ch:match("[%-0-9%.]") then
327                                 return readnumber();
328                         elseif ch == "n" then
329                                 return readliteral("null", null);
330                         elseif ch == "t" then
331                                 return readliteral("true", true);
332                         elseif ch == "f" then
333                                 return readliteral("false", false);
334                         else
335                                 error("invalid character at value start: "..ch);
336                         end
337                 end
338                 error("eof while reading value");
339         end
340         next();
341         return readvalue();
342 end
343
344 function json.test(object)
345         local encoded = json.encode(object);
346         local decoded = json.decode(encoded);
347         local recoded = json.encode(decoded);
348         if encoded ~= recoded then
349                 print("FAILED");
350                 print("encoded:", encoded);
351                 print("recoded:", recoded);
352         else
353                 print(encoded);
354         end
355         return encoded == recoded;
356 end
357
358 return json;