JSON.lua/Source

--JSON.lua

local t = {}

JSON Functions Begin

--JSON Encoder and Parser for Lua 5.1 -- --Copyright 2007 Shaun Brown (http://www.chipmunkav.com) --All Rights Reserved.

--Permission is hereby granted, free of charge, to any person --obtaining a copy of this software to deal in the Software without --restriction, including without limitation the rights to use, --copy, modify, merge, publish, distribute, sublicense, and/or --sell copies of the Software, and to permit persons to whom the --Software is furnished to do so, subject to the following conditions:

--The above copyright notice and this permission notice shall be --included in all copies or substantial portions of the Software. --If you find this software useful please give www.chipmunkav.com a mention.

--THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, --EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES --OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. --IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR --ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF --CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN --CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

local string = string local math = math local table = table local error = error local tonumber = tonumber local tostring = tostring local type = type local setmetatable = setmetatable local pairs = pairs local ipairs = ipairs local assert = assert local Chipmunk = Chipmunk

local StringBuilder = { buffer = {} }

function StringBuilder:New local o = {} setmetatable(o, self) self.__index = self self.si = 0 o.buffer = {} return o end

function StringBuilder:Append(s) self.buffer[#self.buffer+1] = s end

function StringBuilder:ToString return table.concat(self.buffer) end

local JsonWriter = { backslashes = { ['\b'] = "\\b", ['\t'] = "\\t", ['\n'] = "\\n", ['\f'] = "\\f", ['\r'] = "\\r", ['"'] = "\\\"", ['\\'] = "\\\\", ['/'] = "\\/" } }

function JsonWriter:New(pg) local o = {} o.writer = StringBuilder:New setmetatable(o, self) self.__index = self self.pg = pg return o end

function JsonWriter:Append(s) self.writer:Append(s) end

function JsonWriter:ToString return self.writer:ToString end

function JsonWriter:Write(o) local t = type(o) if t == "nil" then self:WriteNil elseif t == "boolean" then self:WriteString(o) elseif t == "number" then self:WriteString(o) elseif t == "string" then self:ParseString(o) elseif t == "table" then self:WriteTable(o) elseif t == "function" then self:WriteFunction(o) elseif t == "thread" then self:WriteError(o) elseif t == "userdata" then self:WriteError(o) end end

function JsonWriter:WriteNil self:Append("null") end

function JsonWriter:WriteString(o) self:Append(tostring(o)) end

function JsonWriter:ParseString(s) self:Append('"') self:Append(string.gsub(s, "[%z%c\\\"/]", function(n) local c = self.backslashes[n] if c then return c end return string.format("\\u%.4X", string.byte(n)) end)) self:Append('"') end

function JsonWriter:IsArray(t) local count = 0 local isindex = function(k) if type(k) == "number" and k > 0 then if math.floor(k) == k then return true end end return false end for k,v in pairs(t) do if self.pg ~= nil then self.pg(k, #t) end if not isindex(k) then return false, '{', '}' else count = math.max(count, k) end end return true, '[', ']', count end

function JsonWriter:WriteTable(t) local ba, st, et, n = self:IsArray(t) self:Append(st) if ba then for i = 1, n do self:Write(t[i]) if i < n then self:Append(',') end end else local first = true; for k, v in pairs(t) do if self.pg ~= nil then self.pg(k, #t, v) end if not first then self:Append(',') end first = false; self:ParseString(k) self:Append(':') self:Write(v) end end self:Append(et) end

function JsonWriter:WriteError(o) error(string.format( "Encoding of %s unsupported", tostring(o))) end

function JsonWriter:WriteFunction(o) if o == Null then self:WriteNil else self:WriteError(o) end end

local StringReader = { s = "", i = 0 }

function StringReader:New(s) local o = {} setmetatable(o, self) self.__index = self o.s = s or o.s return o end

function StringReader:Peek local i = self.i + 1 if self.pg ~= nil then self.pg(self.i, #self.s) end if i <= #self.s then return string.sub(self.s, i, i) end return nil end

function StringReader:Next self.i = self.i+1 if self.i <= #self.s then return string.sub(self.s, self.i, self.i) end return nil end

function StringReader:All return self.s end

local JsonReader = { escapes = { ['t'] = '\t', ['n'] = '\n', ['f'] = '\f', ['r'] = '\r', ['b'] = '\b', } }

function JsonReader:New(s, pg) local o = {} o.reader = StringReader:New(s) setmetatable(o, self) self.__index = self self.s = s return o; end

function JsonReader:Read if self.li == nil then self.li = 0 end self:SkipWhiteSpace local peek = self:Peek if peek == nil then error(string.format( "Nil string: '%s'", self:All)) elseif peek == '{' then return self:ReadObject elseif peek == '[' then return self:ReadArray elseif peek == '"' then return self:ReadString elseif string.find(peek, "[%+%-%d]") then return self:ReadNumber elseif peek == 't' then return self:ReadTrue elseif peek == 'f' then return self:ReadFalse elseif peek == 'n' then return self:ReadNull elseif peek == '/' then self:ReadComment return self:Read else return nil end end

function JsonReader:ReadTrue self:TestReservedWord{'t','r','u','e'} return true end

function JsonReader:ReadFalse self:TestReservedWord{'f','a','l','s','e'} return false end

function JsonReader:ReadNull self:TestReservedWord{'n','u','l','l'} return nil end

function JsonReader:TestReservedWord(t) for i, v in ipairs(t) do if self:Next ~= v then error(string.format( "Error reading '%s': %s", table.concat(t), self:All)) end end end

function JsonReader:ReadNumber local result = self:Next local peek = self:Peek while peek ~= nil and string.find( peek, "[%+%-%d%.eE]") do result = result .. self:Next peek = self:Peek end result = tonumber(result) if result == nil then error(string.format( "Invalid number: '%s'", result)) else return result end end

function JsonReader:ReadString local result = "" assert(self:Next == '"') --self.si = 0 while self:Peek ~= '"' do self.si = self.si + 1 local ch = self:Next if ch == '\\' then ch = self:Next if self.escapes[ch] then ch = self.escapes[ch] end end result = result .. ch end assert(self:Next == '"') local fromunicode = function(m) return string.char(tonumber(m, 16)) end return string.gsub( result, "u%x%x(%x%x)", fromunicode) end

function JsonReader:ReadComment assert(self:Next == '/') local second = self:Next if second == '/' then self:ReadSingleLineComment elseif second == '*' then self:ReadBlockComment else error(string.format( "Invalid comment: %s", self:All)) end end

function JsonReader:ReadBlockComment local done = false while not done do self.si = self.si + 1 local ch = self:Next if ch == '*' and self:Peek == '/' then done = true end if not done and ch == '/' and self:Peek == "*" then error(string.format( "Invalid comment: %s, '/*' illegal.", self:All)) end end self:Next end

function JsonReader:ReadSingleLineComment local ch = self:Next while ch ~= '\r' and ch ~= '\n' do ch = self:Next end end

function JsonReader:ReadArray local result = {} assert(self:Next == '[') local done = false if self:Peek == ']' then done = true; end if not self.si then self.si = 0 end while not done do self.si = self.si + 1 local item = self:Read result[#result+1] = item self:SkipWhiteSpace if self:Peek == ']' then done = true end if not done then local ch = self:Next if ch ~= ',' then error(string.format( "Invalid array: '%s' due to: '%s'", self:All, ch)) end end end assert(']' == self:Next) return result end

function JsonReader:ReadObject local result = {} assert(self:Next == '{') local done = false if self:Peek == '}' then done = true end if not self.si then self.si = 0 end while not done do self.si = self.si + 1 local key = self:Read if type(key) ~= "string" then error(string.format( "Invalid non-string object key: %s", key)) end self:SkipWhiteSpace local ch = self:Next if ch ~= ':' then error(string.format( "Invalid object: '%s' due to: '%s'", self:All, ch)) end self:SkipWhiteSpace local val = self:Read result[key] = val self:SkipWhiteSpace if self:Peek == '}' then done = true end if not done then ch = self:Next if ch ~= ',' then error(string.format( "Invalid array: '%s' near: '%s'", self:All, ch)) end end end assert(self:Next == "}") return result end

function JsonReader:SkipWhiteSpace local p = self:Peek while p ~= nil and string.find(p, "[%s/]") do if p == '/' then self:ReadComment else self:Next end p = self:Peek end end

function JsonReader:Peek return self.reader:Peek end

function JsonReader:Next return self.reader:Next end

function JsonReader:All return self.reader:All end

function Encode(o, pg) local writer = JsonWriter:New(pg) writer.pg = pg writer:Write(o, pg) return writer:ToString end

function Decode(s, pg) local reader = JsonReader:New(s, pg) return reader:Read end

function Null return Null end End JSON Parser

t.DecodeJSON = function(jsonString, ProgFun) if type(jsonString) == "string" then return Decode(jsonString, ProgFun) end print("RbxUtil.DecodeJSON expects string argument!") return nil end

t.EncodeJSON = function(jsonTable, ProgFun) return Encode(jsonTable, ProgFun) end

return t