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