vis-config

lua scripts to configure vis editor

git clone https://9o.is/git/vis-config.git

json.lua

(7731B)


      1 --- Find or provide a suitable json module for vis-lspc.
      2 --
      3 -- @module json
      4 local json = {}
      5 
      6 --- Json modules to use if they are included in LUA_PATH.
      7 local json_impls = {'json', 'cjson', 'dkjson'}
      8 
      9 -- find a suitable json implementation
     10 for _, json_impl in ipairs(json_impls) do
     11   if vis:module_exist(json_impl) then
     12     json = require(json_impl)
     13     if not json.encode or not json.decode then
     14       json = nil
     15     end
     16 
     17     -- found a usable json implementation
     18     if json then
     19       return json
     20     end
     21   end
     22 end
     23 
     24 --[[ json.lua
     25 
     26 A compact pure-Lua JSON library.
     27 The main functions are: json.encode, json.decode.
     28 
     29 ## json.encode:
     30 
     31 This expects the following to be true of any tables being encoded:
     32  * They only have string or number keys. Number keys must be represented as
     33    strings in json; this is part of the json spec.
     34  * They are not recursive. Such a structure cannot be specified in json.
     35 
     36 A Lua table is considered to be an array if and only if its set of keys is a
     37 consecutive sequence of positive integers starting at 1. Arrays are encoded like
     38 so: `[2, 3, false, "hi"]`. Any other type of Lua table is encoded as a json
     39 object, encoded like so: `{"key1": 2, "key2": false}`.
     40 
     41 Because the Lua nil value cannot be a key, and as a table value is considerd
     42 equivalent to a missing key, there is no way to express the json "null" value in
     43 a Lua table. The json.null table can be used to encode the json "null" value.
     44 {key=json.null} will be encoded to `{"key":null}`.
     45 
     46 An empty Lua table, {}, could be considered either a json object or array -
     47 it's an ambiguous edge case. We choose to treat this as an object as it is the
     48 more general type.
     49 
     50 To be clear, none of the above considerations is a limitation of this code.
     51 Rather, it is what we get when we completely observe the json specification for
     52 as arbitrary a Lua object as json is capable of expressing.
     53 
     54 ## json.decode:
     55 
     56 This function parses json, with the exception that it does not pay attention to
     57 \u-escaped unicode code points in strings.
     58 
     59 It is difficult for Lua to return null as a value. In order to prevent the loss
     60 of keys with a null value in a json string, this function uses the one-off
     61 table value json.null (which is just an empty table) to indicate null values.
     62 This way you can check if a value is null with the conditional
     63 `val == json.null`.
     64 
     65 If you have control over the data and are using Lua, I would recommend just
     66 avoiding null values in your data to begin with.
     67 
     68 --]]
     69 
     70 -- Internal functions.
     71 
     72 local function kind_of(obj)
     73   if obj == json.null then
     74     return 'nil'
     75   end
     76   if type(obj) ~= 'table' then return type(obj) end
     77   local i = 1
     78   for _ in pairs(obj) do
     79     if obj[i] ~= nil then i = i + 1 else return 'table' end
     80   end
     81   if i == 1 then return 'table' else return 'array' end
     82 end
     83 
     84 local function escape_str(s)
     85   local in_char  = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'}
     86   local out_char = {'\\', '"', '/',  'b',  'f',  'n',  'r',  't'}
     87   for i, c in ipairs(in_char) do
     88     s = s:gsub(c, '\\' .. out_char[i])
     89   end
     90   return s
     91 end
     92 
     93 -- Returns pos, did_find; there are two cases:
     94 -- 1. Delimiter found: pos = pos after leading space + delim; did_find = true.
     95 -- 2. Delimiter not found: pos = pos after leading space;     did_find = false.
     96 -- This throws an error if err_if_missing is true and the delim is not found.
     97 local function skip_delim(str, pos, delim, err_if_missing)
     98   pos = pos + #str:match('^%s*', pos)
     99   if str:sub(pos, pos) ~= delim then
    100     if err_if_missing then
    101       error('Expected ' .. delim .. ' near position ' .. pos)
    102     end
    103     return pos, false
    104   end
    105   return pos + 1, true
    106 end
    107 
    108 -- Expects the given pos to be the first character after the opening quote.
    109 -- Returns val, pos; the returned pos is after the closing quote character.
    110 local function parse_str_val(str, pos, val)
    111   val = val or ''
    112   local early_end_error = 'End of input found while parsing string.'
    113   if pos > #str then error(early_end_error) end
    114   local c = str:sub(pos, pos)
    115   if c == '"'  then return val, pos + 1 end
    116   if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end
    117   -- We must have a \ character.
    118   local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}
    119   local nextc = str:sub(pos + 1, pos + 1)
    120   if not nextc then error(early_end_error) end
    121   return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc))
    122 end
    123 
    124 -- Returns val, pos; the returned pos is after the number's final character.
    125 local function parse_num_val(str, pos)
    126   local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)
    127   local val = tonumber(num_str)
    128   if not val then error('Error parsing number at position ' .. pos .. '.') end
    129   return val, pos + #num_str
    130 end
    131 
    132 
    133 -- Public values and functions.
    134 
    135 function json.encode(obj, as_key)
    136   local s = {}  -- We'll build the string as an array of strings to be concatenated.
    137   local kind = kind_of(obj)  -- This is 'array' if it's an array or type(obj) otherwise.
    138   if kind == 'array' then
    139     if as_key then error('Can\'t encode array as key.') end
    140     s[#s + 1] = '['
    141     for i, val in ipairs(obj) do
    142       if i > 1 then s[#s + 1] = ', ' end
    143       s[#s + 1] = json.encode(val)
    144     end
    145     s[#s + 1] = ']'
    146   elseif kind == 'table' then
    147     if as_key then error('Can\'t encode table as key.') end
    148     s[#s + 1] = '{'
    149     for k, v in pairs(obj) do
    150       if #s > 1 then s[#s + 1] = ', ' end
    151       s[#s + 1] = json.encode(k, true)
    152       s[#s + 1] = ':'
    153       s[#s + 1] = json.encode(v)
    154     end
    155     s[#s + 1] = '}'
    156   elseif kind == 'string' then
    157     return '"' .. escape_str(obj) .. '"'
    158   elseif kind == 'number' then
    159     if as_key then return '"' .. tostring(obj) .. '"' end
    160     return tostring(obj)
    161   elseif kind == 'boolean' then
    162     return tostring(obj)
    163   elseif kind == 'nil' then
    164     return 'null'
    165   else
    166     error('Unjsonifiable type: ' .. kind .. '.')
    167   end
    168   return table.concat(s)
    169 end
    170 
    171 json.null = {}  -- This is a one-off table to represent the null value.
    172 
    173 function json.decode(str, pos, end_delim)
    174   pos = pos or 1
    175   if pos > #str then error('Reached unexpected end of input.') end
    176   local pos = pos + #str:match('^%s*', pos)  -- Skip whitespace.
    177   local first = str:sub(pos, pos)
    178   if first == '{' then  -- Parse an object.
    179     local obj, key, delim_found = {}, true, true
    180     pos = pos + 1
    181     while true do
    182       key, pos = json.decode(str, pos, '}')
    183       if key == nil then return obj, pos end
    184       if not delim_found then error('Comma missing between object items.') end
    185       pos = skip_delim(str, pos, ':', true)  -- true -> error if missing.
    186       obj[key], pos = json.decode(str, pos)
    187       pos, delim_found = skip_delim(str, pos, ',')
    188     end
    189   elseif first == '[' then  -- Parse an array.
    190     local arr, val, delim_found = {}, true, true
    191     pos = pos + 1
    192     while true do
    193       val, pos = json.decode(str, pos, ']')
    194       if val == nil then return arr, pos end
    195       if not delim_found then error('Comma missing between array items.') end
    196       arr[#arr + 1] = val
    197       pos, delim_found = skip_delim(str, pos, ',')
    198     end
    199   elseif first == '"' then  -- Parse a string.
    200     return parse_str_val(str, pos + 1)
    201   elseif first == '-' or first:match('%d') then  -- Parse a number.
    202     return parse_num_val(str, pos)
    203   elseif first == end_delim then  -- End of an object or array.
    204     return nil, pos + 1
    205   else  -- Parse true, false, or null.
    206     local literals = {['true'] = true, ['false'] = false, ['null'] = json.null}
    207     for lit_str, lit_val in pairs(literals) do
    208       local lit_end = pos + #lit_str - 1
    209       if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end
    210     end
    211     local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)
    212     error('Invalid json syntax starting at ' .. pos_info_str)
    213   end
    214 end
    215 
    216 return json