134 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
		
		
			
		
	
	
			134 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| 
								 | 
							
								local json = {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								local 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
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								local 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
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								local 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
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								local 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
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								local 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
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								json.null = {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function json.parse(str, pos, end_delim)
							 | 
						||
| 
								 | 
							
								  pos = pos or 1
							 | 
						||
| 
								 | 
							
								  if pos > #str then error('Reached unexpected end of input.') end
							 | 
						||
| 
								 | 
							
								  local 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
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								return json
							 |