Merge 0.9->trunk
[prosody.git] / util / json.lua
index 05453703369e35f642b11acd61c33f93c5e2a97e..ff7351a72f991dcb56ade78eb80c7d5e1153b8b3 100644 (file)
@@ -1,6 +1,15 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+--
+-- utf8char copyright (C) 2007 Rici Lake
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
 
 local type = type;
-local t_insert, t_concat, t_remove = table.insert, table.concat, table.remove;
+local t_insert, t_concat, t_remove, t_sort = table.insert, table.concat, table.remove, table.sort;
 local s_char = string.char;
 local tostring, tonumber = tostring, tonumber;
 local pairs, ipairs = pairs, ipairs;
@@ -29,6 +38,32 @@ for i=0,31 do
        if not escapes[ch] then escapes[ch] = ("\\u%.4X"):format(i); end
 end
 
+local function utf8char(i)
+       if i >= 0 then
+               i = i - i%1
+               if i < 128 then
+                       return s_char(i)
+               else
+                       local c1 = i % 64
+                       i = (i - c1) / 64
+                       if i < 32 then
+                               return s_char(0xC0+i, 0x80+c1)
+                       else
+                               local c2 = i % 64
+                               i = (i - c2) / 64
+                               if i < 16 and (i ~= 13 or c2 < 32) then
+                                       return s_char(0xE0+i, 0x80+c2, 0x80+c1)
+                               elseif i >= 16 and i < 0x110 then
+                                       local c3 = i % 64
+                                       i = (i - c3) / 64
+                                       return s_char(0xF0+i, 0x80+c3, 0x80+c2, 0x80+c1)
+                               end
+                       end
+               end
+       end
+end
+
+
 local valid_types = {
        number  = true,
        string  = true,
@@ -79,11 +114,25 @@ function tablesave(o, buffer)
        if next(__hash) ~= nil or next(hash) ~= nil or next(__array) == nil then
                t_insert(buffer, "{");
                local mark = #buffer;
-               for k,v in pairs(hash) do
-                       stringsave(k, buffer);
-                       t_insert(buffer, ":");
-                       simplesave(v, buffer);
-                       t_insert(buffer, ",");
+               if buffer.ordered then
+                       local keys = {};
+                       for k in pairs(hash) do
+                               t_insert(keys, k);
+                       end
+                       t_sort(keys);
+                       for _,k in ipairs(keys) do
+                               stringsave(k, buffer);
+                               t_insert(buffer, ":");
+                               simplesave(hash[k], buffer);
+                               t_insert(buffer, ",");
+                       end
+               else
+                       for k,v in pairs(hash) do
+                               stringsave(k, buffer);
+                               t_insert(buffer, ":");
+                               simplesave(v, buffer);
+                               t_insert(buffer, ",");
+                       end
                end
                if next(__hash) ~= nil then
                        t_insert(buffer, "\"__hash\":[");
@@ -129,17 +178,29 @@ function json.encode(obj)
        simplesave(obj, t);
        return t_concat(t);
 end
+function json.encode_ordered(obj)
+       local t = { ordered = true };
+       simplesave(obj, t);
+       return t_concat(t);
+end
+function json.encode_array(obj)
+       local t = {};
+       arraysave(obj, t);
+       return t_concat(t);
+end
 
 -----------------------------------
 
 
 function json.decode(json)
+       json = json.." "; -- appending a space ensures valid json wouldn't touch EOF
        local pos = 1;
        local current = {};
        local stack = {};
        local ch, peek;
        local function next()
                ch = json:sub(pos, pos);
+               if ch == "" then error("Unexpected EOF"); end
                pos = pos+1;
                peek = json:sub(pos, pos);
                return ch;
@@ -166,7 +227,7 @@ function json.decode(json)
                        skipwhitespace();
                        if ch == "/" and peek == "*" then
                                skipstarcomment();
-                       elseif ch == "/" and peek == "*" then
+                       elseif ch == "/" and peek == "/" then
                                skiplinecomment();
                        else
                                return;
@@ -223,7 +284,7 @@ function json.decode(json)
                                                if not ch:match("[0-9a-fA-F]") then error("invalid unicode escape sequence in string"); end
                                                seq = seq..ch;
                                        end
-                                       s = s..s.char(tonumber(seq, 16)); -- FIXME do proper utf-8
+                                       s = s..utf8char(tonumber(seq, 16));
                                        next();
                                else error("invalid escape sequence in string"); end
                        end