1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-24 06:20:05 +08:00
SpaceVim/lua/spacevim/api/data/toml.lua
2023-08-05 22:31:52 +08:00

379 lines
9.2 KiB
Lua

--=============================================================================
-- toml.lua --- toml lua api
-- Copyright (c) 2016-2023 Wang Shidong & Contributors
-- Author: Wang Shidong < wsdjeg@outlook.com >
-- URL: https://spacevim.org
-- License: GPLv3
--=============================================================================
local M = {}
function M.parse(text)
local input = {
text = text,
p = 0,
length = vim.fn.strlen(text),
}
return M._parse(input)
end
function M.parse_file(filename)
if vim.fn.filereadable(filename) == 0 then
error('toml API: No such file ' .. filename)
end
local text = table.concat(vim.fn.readfile(filename), '\n')
return M.parse(vim.fn.iconv(text, 'utf8', vim.o.encoding))
end
local skip_pattern = [[\C^\%(\%(\s\|\r\?\n\)\+\|#[^\r\n]*\)]]
local bare_key_pattern = [[\%([A-Za-z0-9_-]\+\)]]
function M._skip(input)
while M._match(input, [[\%(\s\|\r\?\n\|#\)]]) do
input.p = vim.fn.matchend(input.text, skip_pattern, input.p)
end
end
local regex_prefix = ''
if vim.fn.exists('+regexpengine') == 1 then
regex_prefix = '\\%#=1\\C^'
else
regex_prefix = '\\C^'
end
function M._consume(input, pattern)
M._skip(input)
local _end = vim.fn.matchend(input.text, regex_prefix .. pattern, input.p)
if _end == -1 then
M._error(input)
elseif _end == input.p then
return ''
end
local matched = vim.fn.strpart(input.text, input.p, _end - input.p)
input.p = _end
return matched
end
function M._match(input, pattern)
return vim.fn.match(input.text, regex_prefix .. pattern, input.p) ~= -1
end
function M._eof(input)
return input.p >= input.length
end
function M._error(input)
local s = vim.fn.matchstr(input.text, regex_prefix .. [[.\{-}\ze\%(\r\?\n\|$\)]], input.p)
s = vim.fn.substitute(s, '\\r', '\\\\r', 'g')
error('toml API: Illegal TOML format at ' .. s)
end
function M._parse(input)
local data = {}
M._skip(input)
while not M._eof(input) do
if M._match(input, '[^ [:tab:]#.[\\]]') then
local keys = M._keys(input, '=')
M._equals(input)
local value = M._value(input)
M._put_dict(data, keys, value)
elseif M._match(input, '\\[\\[') then
local keys, value = M._array_of_tables(input)
M._put_array(data, keys, value)
elseif M._match(input, '\\[') then
local keys, value = M._table(input)
M._put_dict(data, keys, value)
else
M._error(input)
end
M._skip(input)
end
return data
end
function M._keys(input, e)
local keys = {}
while not M._eof(input) and not M._match(input, e) do
M._skip(input)
local key
if M._match(input, '"') then
key = M._basic_string(input)
elseif M._match(input, "'") then
key = M._literal(input)
else
key = M._consume(input, bare_key_pattern)
end
if key then
table.insert(keys, key)
end
M._consume(input, '\\.\\?')
end
if #keys == 0 then
M._error(input)
end
return keys
end
function M._equals(input)
M._consume(input, '=')
return '='
end
function M._value(input)
M._skip(input)
if M._match(input, '"\\{3}') then
return M._multiline_basic_string(input)
elseif M._match(input, '"\\{1}') then
return M._basic_string(input)
elseif M._match(input, "'\\{3}") then
return M._multiline_literal(input)
elseif M._match(input, "'\\{1}") then
return M._literal(input)
elseif M._match(input, '\\[') then
return M._array(input)
elseif M._match(input, '{') then
return M._inline_table(input)
elseif M._match(input, '\\%(true\\|false\\)') then
return M._boolean(input)
elseif M._match(input, '\\d\\{4}-') then
return M._datetime(input)
elseif M._match(input, '\\d\\{2}:') then
return M._local_time(input)
elseif M._match(input, [[[+-]\?\d\+\%(_\d\+\)*\%(\.\d\+\%(_\d\+\)*\|\%(\.\d\+\%(_\d\+\)*\)\?[eE]\)]]) then
return M._float(input)
elseif M._match(input, [[[+-]\?\%(inf\|nan\)]]) then
return M._special_float(input)
else
return M._integer(input)
end
end
function M._basic_string(input)
local s = M._consume(input, [["\%(\\"\|[^"]\)*"]])
s = string.sub(s, 2, #s - 1)
return M._unescape(s)
end
function M._multiline_basic_string(input)
local s = M._consume(input, [["\{3}\%(\\.\|\_.\)\{-}"\{,2}"\{3}]])
s = string.sub(s, 4, #s - 3)
s = vim.fn.substitute(s, [[^\r\?\n]], '', '')
s = vim.fn.substitute(s, [[\\\%(\s\|\r\?\n\)*]], '', 'g')
return M._unescape(s)
end
function M._literal(input)
local s = M._consume(input, "'[^']*'")
return string.sub(s, 2, #s - 1)
end
function M._multiline_literal(input)
local s = M._consume(input, [['\{3}.\{-}'\{,2}'\{3}]])
s = string.sub(s, 4, #s - 3)
s = vim.fn.substitute(s, [[^\r\?\n]], '', '')
return s
end
function M._integer(input)
local s, base
if M._match(input, '0b') then
s = M._consume(input, [[0b[01]\+\%(_[01]\+\)*]])
base = 2
elseif M._match(input, '0o') then
s = M._consume(input, [[0o[0-7]\+\%(_[0-7]\+\)*]])
s = string.sub(s, 3)
base = 8
elseif M._match(input, '0x') then
s = M._consume(input, [['0x[A-Fa-f0-9]\+\%(_[A-Fa-f0-9]\+\)*]])
base = 16
else
s = M._consume(input, [[[+-]\?\d\+\%(_\d\+\)*]])
base = 10
end
s = vim.fn.substitute(s, '_', '', 'g')
return vim.fn.str2nr(s, base)
end
function M._float(input)
local s = M._consume(input, [[[+-]\?[0-9._]\+\%([eE][+-]\?\d\+\%(_\d\+\)*\)\?]])
s = vim.fn.substitute(s, '_', '', 'g')
return vim.fn.str2float(s)
end
function M._special_float(input)
local s = M._consume(input, [[[+-]\?\%(inf\|nan\)]])
s = vim.fn.substitute(s, '_', '', 'g')
return vim.fn.str2float(s)
end
function M._boolean(input)
local s = M._consume(input, [[\%(true\|false\)]])
return s == 'true'
end
function M._datetime(input)
local s = M._consume(
input,
[[\d\{4}-\d\{2}-\d\{2}\%([T ]\d\{2}:\d\{2}:\d\{2}\%(\.\d\+\)\?\%(Z\|[+-]\d\{2}:\d\{2}\)\?\)\?]]
)
return s
end
function M._local_time(input)
return M._consume(input, [[\d\{2}:\d\{2}:\d\{2}\%(\.\d\+\)\?]])
end
function M._array(input)
local ary = {}
M._consume(input, '\\[')
M._skip(input)
while not M._eof(input) and not M._match(input, '\\]') do
table.insert(ary, M._value(input))
M._consume(input, ',\\?')
M._skip(input)
end
M._consume(input, '\\]')
return ary
end
function M._table(input)
local tbl = {}
M._consume(input, '\\[')
local name = M._keys(input, '\\]')
M._consume(input, '\\]')
M._skip(input)
while not M._eof(input) and not M._match(input, '\\[') do
local keys = M._keys(input, '=')
M._equals(input)
local value = M._value(input)
M._put_dict(tbl, keys, value)
M._skip(input)
end
return name, tbl
end
function M._inline_table(input)
local tbl = {}
M._consume(input, '{')
while not M._eof(input) and not M._match(input, '}') do
local keys = M._keys(input, '=')
M._equals(input)
local value = M._value(input)
M._put_dict(tbl, keys, value)
M._skip(input)
end
M._consume(input, '}')
return tbl
end
function M._array_of_tables(input)
local tbl = {}
M._consume(input, '\\[\\[')
local name = M._keys(input, '\\]\\]')
M._consume(input, '\\]\\]')
M._skip(input)
while not M._eof(input) and not M._match(input, '\\[') do
local keys = M._keys(input, '=')
M._equals(input)
local value = M._value(input)
M._put_dict(tbl, keys, value)
M._skip(input)
end
return name, tbl
end
function M._unescape(text)
text = vim.fn.substitute(text, '\\\\"', '"', 'g')
text = vim.fn.substitute(text, '\\\\b', '\b', 'g')
text = vim.fn.substitute(text, '\\\\t', '\t', 'g')
text = vim.fn.substitute(text, '\\\\n', '\n', 'g')
text = vim.fn.substitute(text, '\\\\f', '\f', 'g')
text = vim.fn.substitute(text, '\\\\r', '\r', 'g')
text = vim.fn.substitute(text, '\\\\/', '/', 'g')
text = vim.fn.substitute(text, '\\\\\\\\', '\\', 'g')
text = vim.fn.substitute(text, '\\C\\\\u\\(\\x\\{4}\\)', '\\=s:_nr2char("0x" . submatch(1))', 'g')
text = vim.fn.substitute(text, '\\C\\U\\(\\x\\{8}\\)', '\\=s:_nr2char("0x" . submatch(1))', 'g')
return text
end
function M._nr2char(nr)
return vim.fn.iconv(vim.fn.nr2char(nr), vim.o.encoding, 'utf8')
end
local function is_list(t)
return vim.tbl_islist(t)
end
local function is_table(t)
return vim.fn.type(t) == 4
end
local function has_key(t, k)
if type(t) == 'table' and t[k] ~= nil then
return true
else
return false
end
end
function M._put_dict(dict, keys, value)
local ref = dict
local i = 1
for _, key in ipairs(keys) do
if i == #keys then
break
end
if has_key(ref, key) and is_table(ref[key]) then
ref = ref[key]
elseif has_key(ref, key) and is_list(ref[key]) then
ref = ref[key][#ref[key]]
else
ref[key] = {}
ref = ref[key]
end
i = i + 1
end
if is_table(ref) and vim.fn.has_key(ref, keys[#keys])
and is_table(ref[keys[#keys]])
and is_table(value) then
vim.fn.extend(ref[keys[#keys]], value)
else
ref[keys[#keys]] = value
end
end
function M._put_array(dict, keys, value)
local ref = dict
local i = 1
for _, key in ipairs(keys) do
if i == #keys then
break
end
ref[key] = ref[key] or {}
if is_list(ref[key]) then
ref = ref[key][#ref[key]]
else
ref = ref[key]
end
i = i + 1
end
ref[keys[#keys]] = ref[keys[#keys]] or {}
table.insert(ref[keys[#keys]], value)
end
return M