mirror of
https://github.com/neovim/neovim.git
synced 2024-12-24 13:15:09 -07:00
bda63d5b97
This is a breaking change which will make refactor of typval and shada code a lot easier. In particular, code that would use or check for v:msgpack_types.binary in the wild would be broken. This appears to be rarely used in existing plugins. Also some cases where v:msgpack_type.string would be used to represent a binary string of "string" type, we use a BLOB instead, which is vimscripts native type for binary blobs, and already was used for BIN formats when necessary. msgpackdump(msgpackparse(data)) no longer preserves the distinction of BIN and STR strings. This is very common behavior for language-specific msgpack bindings. Nvim uses msgpack as a tool to serialize its data. Nvim is not a tool to bit-perfectly manipulate arbitrary msgpack data out in the wild. The changed tests should indicate how behavior changes in various edge cases.
831 lines
26 KiB
VimL
831 lines
26 KiB
VimL
if exists('g:loaded_msgpack_autoload')
|
|
finish
|
|
endif
|
|
let g:loaded_msgpack_autoload = 1
|
|
|
|
""
|
|
" Check that given value is an integer. Respects |msgpack-special-dict|.
|
|
function msgpack#is_int(v) abort
|
|
return type(a:v) == type(0) || (
|
|
\type(a:v) == type({}) && get(a:v, '_TYPE') is# v:msgpack_types.integer)
|
|
endfunction
|
|
|
|
""
|
|
" Check that given value is an unsigned integer. Respects
|
|
" |msgpack-special-dict|.
|
|
function msgpack#is_uint(v) abort
|
|
return msgpack#is_int(a:v) && (type(a:v) == type(0)
|
|
\? a:v >= 0
|
|
\: a:v._VAL[0] > 0)
|
|
endfunction
|
|
|
|
""
|
|
" True if s:msgpack_init_python() function was already run.
|
|
let s:msgpack_python_initialized = 0
|
|
|
|
""
|
|
" Cached return of s:msgpack_init_python() used when
|
|
" s:msgpack_python_initialized is true.
|
|
let s:msgpack_python_type = 0
|
|
|
|
""
|
|
" Create Python functions that are necessary for work. Also defines functions
|
|
" s:msgpack_dict_strftime(format, timestamp) and s:msgpack_dict_strptime(format,
|
|
" string).
|
|
"
|
|
" @return Zero in case no Python is available, empty string if Python-2 is
|
|
" available and string `"3"` if Python-3 is available.
|
|
function s:msgpack_init_python() abort
|
|
if s:msgpack_python_initialized
|
|
return s:msgpack_python_type
|
|
endif
|
|
let s:msgpack_python_initialized = 1
|
|
for suf in (has('win32') ? ['3'] : ['', '3'])
|
|
try
|
|
execute 'python' . suf
|
|
\. "\n"
|
|
\. "def shada_dict_strftime():\n"
|
|
\. " import datetime\n"
|
|
\. " import vim\n"
|
|
\. " fmt = vim.eval('a:format')\n"
|
|
\. " timestamp = vim.eval('a:timestamp')\n"
|
|
\. " timestamp = [int(v) for v in timestamp['_VAL']]\n"
|
|
\. " timestamp = timestamp[0] * (timestamp[1] << 62\n"
|
|
\. " | timestamp[2] << 31\n"
|
|
\. " | timestamp[3])\n"
|
|
\. " time = datetime.datetime.fromtimestamp(timestamp)\n"
|
|
\. " return time.strftime(fmt)\n"
|
|
\. "def shada_dict_strptime():\n"
|
|
\. " import calendar\n"
|
|
\. " import datetime\n"
|
|
\. " import vim\n"
|
|
\. " fmt = vim.eval('a:format')\n"
|
|
\. " timestr = vim.eval('a:string')\n"
|
|
\. " timestamp = datetime.datetime.strptime(timestr, fmt)\n"
|
|
\. " try:\n"
|
|
\. " timestamp = int(timestamp.timestamp())\n"
|
|
\. " except:\n"
|
|
\. " try:\n"
|
|
\. " timestamp = int(timestamp.strftime('%s'))\n"
|
|
\. " except:\n"
|
|
\. " timestamp = calendar.timegm(timestamp.utctimetuple())\n"
|
|
\. " if timestamp > 2 ** 31:\n"
|
|
\. " tsabs = abs(timestamp)\n"
|
|
\. " return ('{\"_TYPE\": v:msgpack_types.integer,'\n"
|
|
\. " + '\"_VAL\": [{sign},{v1},{v2},{v3}]}').format(\n"
|
|
\. " sign=(1 if timestamp >= 0 else -1),\n"
|
|
\. " v1=((tsabs >> 62) & 0x3),\n"
|
|
\. " v2=((tsabs >> 31) & (2 ** 31 - 1)),\n"
|
|
\. " v3=(tsabs & (2 ** 31 - 1)))\n"
|
|
\. " else:\n"
|
|
\. " return str(timestamp)\n"
|
|
execute "function s:msgpack_dict_strftime(format, timestamp) abort\n"
|
|
\. " return py" . suf . "eval('shada_dict_strftime()')\n"
|
|
\. "endfunction\n"
|
|
\. "function s:msgpack_dict_strptime(format, string)\n"
|
|
\. " return eval(py" . suf . "eval('shada_dict_strptime()'))\n"
|
|
\. "endfunction\n"
|
|
let s:msgpack_python_type = suf
|
|
return suf
|
|
catch
|
|
continue
|
|
endtry
|
|
endfor
|
|
|
|
""
|
|
" strftime() function for |msgpack-special-dict| values.
|
|
"
|
|
" @param[in] format String according to which time should be formatted.
|
|
" @param[in] timestamp Timestamp (seconds since epoch) to format.
|
|
"
|
|
" @return Formatted timestamp.
|
|
"
|
|
" @warning Without +python or +python3 this function does not work correctly.
|
|
" The Vimscript code contains “reference” implementation which does
|
|
" not really work because of precision loss.
|
|
function s:msgpack_dict_strftime(format, timestamp)
|
|
return msgpack#strftime(a:format, +msgpack#int_dict_to_str(a:timestamp))
|
|
endfunction
|
|
|
|
""
|
|
" Function that parses given string according to given format.
|
|
"
|
|
" @param[in] format String according to which string was formatted.
|
|
" @param[in] string Time formatted according to format.
|
|
"
|
|
" @return Timestamp.
|
|
"
|
|
" @warning Without +python or +python3 this function is able to work only with
|
|
" 31-bit (32-bit signed) timestamps that have format
|
|
" `%Y-%m-%dT%H:%M:%S`.
|
|
function s:msgpack_dict_strptime(format, string)
|
|
let fmt = '%Y-%m-%dT%H:%M:%S'
|
|
if a:format isnot# fmt
|
|
throw 'notimplemented-format:Only ' . fmt . ' format is supported'
|
|
endif
|
|
let match = matchlist(a:string,
|
|
\'\v\C^(\d+)\-(\d+)\-(\d+)T(\d+)\:(\d+)\:(\d+)$')
|
|
if empty(match)
|
|
throw 'invalid-string:Given string does not match format ' . a:format
|
|
endif
|
|
call map(match, 'str2nr(v:val, 10)')
|
|
let [year, month, day, hour, minute, second] = match[1:6]
|
|
" Bisection start and end:
|
|
"
|
|
" Start: 365 days in year, 28 days in month, -12 hours tz offset.
|
|
let bisect_ts_start = (((((year - 1970) * 365
|
|
\+ (month - 1) * 28
|
|
\+ (day - 1)) * 24
|
|
\+ hour - 12) * 60
|
|
\+ minute) * 60
|
|
\+ second)
|
|
if bisect_ts_start < 0
|
|
let bisect_ts_start = 0
|
|
endif
|
|
let start_string = strftime(fmt, bisect_ts_start)
|
|
if start_string is# a:string
|
|
return bisect_ts_start
|
|
endif
|
|
" End: 366 days in year, 31 day in month, +14 hours tz offset.
|
|
let bisect_ts_end = (((((year - 1970) * 366
|
|
\+ (month - 1) * 31
|
|
\+ (day - 1)) * 24
|
|
\+ hour + 14) * 60
|
|
\+ minute) * 60
|
|
\+ second)
|
|
let end_string = strftime(fmt, bisect_ts_end)
|
|
if end_string is# a:string
|
|
return bisect_ts_end
|
|
endif
|
|
if start_string ># end_string
|
|
throw 'internal-start-gt:Internal error: start > end'
|
|
endif
|
|
if start_string is# end_string
|
|
throw printf('internal-start-eq:Internal error: '
|
|
\. 'start(%u)==end(%u), but start(%s)!=string(%s)',
|
|
\bisect_ts_start, bisect_ts_end,
|
|
\string(start_string), string(a:string))
|
|
endif
|
|
if start_string ># a:string
|
|
throw 'internal-start-string:Internal error: start > string'
|
|
endif
|
|
if end_string <# a:string
|
|
throw 'internal-end-string:Internal error: end < string'
|
|
endif
|
|
while 1
|
|
let bisect_ts_middle = (bisect_ts_start/2) + (bisect_ts_end/2)
|
|
let middle_string = strftime(fmt, bisect_ts_middle)
|
|
if a:string is# middle_string
|
|
return bisect_ts_middle
|
|
elseif a:string ># middle_string
|
|
if bisect_ts_middle == bisect_ts_start
|
|
let bisect_ts_start += 1
|
|
else
|
|
let bisect_ts_start = bisect_ts_middle
|
|
endif
|
|
else
|
|
if bisect_ts_middle == bisect_ts_end
|
|
let bisect_ts_end -= 1
|
|
else
|
|
let bisect_ts_end = bisect_ts_middle
|
|
endif
|
|
endif
|
|
if bisect_ts_start >= bisect_ts_end
|
|
throw 'not-found:Unable to find timestamp'
|
|
endif
|
|
endwhile
|
|
endfunction
|
|
|
|
return 0
|
|
endfunction
|
|
|
|
""
|
|
" Wrapper for strftime() that respects |msgpack-special-dict|. May actually use
|
|
" non-standard strftime() implementations for |msgpack-special-dict| values.
|
|
"
|
|
" @param[in] format Format string.
|
|
" @param[in] timestamp Formatted timestamp.
|
|
function msgpack#strftime(format, timestamp) abort
|
|
if type(a:timestamp) == type({})
|
|
call s:msgpack_init_python()
|
|
return s:msgpack_dict_strftime(a:format, a:timestamp)
|
|
else
|
|
return strftime(a:format, a:timestamp)
|
|
endif
|
|
endfunction
|
|
|
|
""
|
|
" Parse string according to the format.
|
|
"
|
|
" Requires +python available. If it is not then only supported format is
|
|
" `%Y-%m-%dT%H:%M:%S` because this is the format used by ShaDa plugin. Also in
|
|
" this case bisection will be used (timestamps tried with strftime() up until
|
|
" result matches the string) and only 31-bit (signed 32-bit: with negative
|
|
" timestamps being useless this leaves 31 bits) timestamps will be supported.
|
|
"
|
|
" @param[in] format Time format.
|
|
" @param[in] string Parsed time string. Must match given format.
|
|
"
|
|
" @return Timestamp. Possibly as |msgpack-special-dict|.
|
|
function msgpack#strptime(format, string) abort
|
|
call s:msgpack_init_python()
|
|
return s:msgpack_dict_strptime(a:format, a:string)
|
|
endfunction
|
|
|
|
let s:MSGPACK_HIGHEST_BIT = 1
|
|
let s:MSGPACK_HIGHEST_BIT_NR = 0
|
|
while s:MSGPACK_HIGHEST_BIT * 2 > 0
|
|
let s:MSGPACK_HIGHEST_BIT = s:MSGPACK_HIGHEST_BIT * 2
|
|
let s:MSGPACK_HIGHEST_BIT_NR += 1
|
|
endwhile
|
|
|
|
""
|
|
" Shift given number by given amount of bits
|
|
function s:shift(n, s) abort
|
|
if a:s == 0
|
|
return a:n
|
|
elseif a:s < 0
|
|
let ret = a:n
|
|
for _ in range(-a:s)
|
|
let ret = ret / 2
|
|
endfor
|
|
return ret
|
|
else
|
|
let ret = a:n
|
|
for i in range(a:s)
|
|
let new_ret = ret * 2
|
|
if new_ret < ret
|
|
" Overflow: remove highest bit
|
|
let ret = xor(s:MSGPACK_HIGHEST_BIT, ret) * 2
|
|
endif
|
|
let ret = new_ret
|
|
endfor
|
|
return ret
|
|
endif
|
|
endfunction
|
|
|
|
let s:msgpack_mask_cache = {
|
|
\s:MSGPACK_HIGHEST_BIT_NR : s:MSGPACK_HIGHEST_BIT - 1}
|
|
|
|
""
|
|
" Apply a mask where first m bits are ones and other are zeroes to a given
|
|
" number
|
|
function s:mask1(n, m) abort
|
|
if a:m > s:MSGPACK_HIGHEST_BIT_NR + 1
|
|
let m = s:MSGPACK_HIGHEST_BIT_NR + 1
|
|
else
|
|
let m = a:m
|
|
endif
|
|
if !has_key(s:msgpack_mask_cache, m)
|
|
let p = 0
|
|
for _ in range(m)
|
|
let p = p * 2 + 1
|
|
endfor
|
|
let s:msgpack_mask_cache[m] = p
|
|
endif
|
|
return and(a:n, s:msgpack_mask_cache[m])
|
|
endfunction
|
|
|
|
""
|
|
" Convert |msgpack-special-dict| that represents integer value to a string. Uses
|
|
" hexadecimal representation starting with 0x because it is the easiest to
|
|
" convert to.
|
|
function msgpack#int_dict_to_str(v) abort
|
|
let v = a:v._VAL
|
|
" 64-bit number:
|
|
" 0000000001111111111222222222233333333334444444444555555555566666
|
|
" 1234567890123456789012345678901234567890123456789012345678901234
|
|
" Split in _VAL:
|
|
" 0000000001111111111222222222233 3333333344444444445555555555666 66
|
|
" 1234567890123456789012345678901 2345678901234567890123456789012 34
|
|
" Split by hex digits:
|
|
" 0000 0000 0111 1111 1112 2222 2222 2333 3333 3334 4444 4444 4555 5555 5556 6666
|
|
" 1234 5678 9012 3456 7890 1234 5678 9012 3456 7890 1234 5678 9012 3456 7890 1234
|
|
"
|
|
" Total split:
|
|
" _VAL[3] _VAL[2] _VAL[1]
|
|
" ______________________________________ _______________________________________ __
|
|
" 0000 0000 0111 1111 1112 2222 2222 233 3 3333 3334 4444 4444 4555 5555 5556 66 66
|
|
" 1234 5678 9012 3456 7890 1234 5678 901 2 3456 7890 1234 5678 9012 3456 7890 12 34
|
|
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^
|
|
" g4 g3 g2 g1
|
|
" ********************************** *** * ********************************** ** **
|
|
" 1 2 3 4 5 6
|
|
" 1: s:mask1(v[3], 28): first 28 bits of _VAL[3]
|
|
" 2: s:shift(v[3], -28): last 3 bits of _VAL[3]
|
|
" 3: s:mask1(v[2], 1): first bit of _VAL[2]
|
|
" 4: s:mask1(s:shift(v[2], -1), 28): bits 2 .. 29 of _VAL[2]
|
|
" 5: s:shift(v[2], -29): last 2 bits of _VAL[2]
|
|
" 6: s:shift(v[1], 2): _VAL[1]
|
|
let g4 = printf('%07x', s:mask1(v[3], 28))
|
|
let g3 = printf('%01x', or(s:shift(v[3], -28), s:shift(s:mask1(v[2], 1), 3)))
|
|
let g2 = printf('%07x', s:mask1(s:shift(v[2], -1), 28))
|
|
let g1 = printf('%01x', or(s:shift(v[2], -29), s:shift(v[1], 2)))
|
|
return ((v[0] < 0 ? '-' : '') . '0x' . g1 . g2 . g3 . g4)
|
|
endfunction
|
|
|
|
""
|
|
" True boolean value.
|
|
let g:msgpack#true = {'_TYPE': v:msgpack_types.boolean, '_VAL': 1}
|
|
lockvar! g:msgpack#true
|
|
|
|
""
|
|
" False boolean value.
|
|
let g:msgpack#false = {'_TYPE': v:msgpack_types.boolean, '_VAL': 0}
|
|
lockvar! g:msgpack#false
|
|
|
|
""
|
|
" NIL value.
|
|
let g:msgpack#nil = {'_TYPE': v:msgpack_types.nil, '_VAL': 0}
|
|
lockvar! g:msgpack#nil
|
|
|
|
""
|
|
" Deduce type of |msgpack-special-dict|.
|
|
"
|
|
" @return zero if given dictionary is not special or name of the key in
|
|
" v:msgpack_types dictionary.
|
|
function msgpack#special_type(v) abort
|
|
if type(a:v) != type({}) || !has_key(a:v, '_TYPE')
|
|
return 0
|
|
endif
|
|
for [k, v] in items(v:msgpack_types)
|
|
if a:v._TYPE is v
|
|
return k
|
|
endif
|
|
endfor
|
|
return 0
|
|
endfunction
|
|
|
|
""
|
|
" Mapping that maps type() output to type names.
|
|
let s:MSGPACK_STANDARD_TYPES = {
|
|
\type(0): 'integer',
|
|
\type(0.0): 'float',
|
|
\type(''): 'string',
|
|
\type([]): 'array',
|
|
\type({}): 'map',
|
|
\type(v:true): 'boolean',
|
|
\type(v:null): 'nil',
|
|
\}
|
|
|
|
""
|
|
" Deduce type of one of items returned by msgpackparse().
|
|
"
|
|
" @return Name of a key in v:msgpack_types.
|
|
function msgpack#type(v) abort
|
|
let special_type = msgpack#special_type(a:v)
|
|
if special_type is 0
|
|
return s:MSGPACK_STANDARD_TYPES[type(a:v)]
|
|
endif
|
|
return special_type
|
|
endfunction
|
|
|
|
""
|
|
" Dump nil value.
|
|
function s:msgpack_dump_nil(v) abort
|
|
return 'NIL'
|
|
endfunction
|
|
|
|
""
|
|
" Dump boolean value.
|
|
function s:msgpack_dump_boolean(v) abort
|
|
return (a:v is v:true || (a:v isnot v:false && a:v._VAL)) ? 'TRUE' : 'FALSE'
|
|
endfunction
|
|
|
|
""
|
|
" Dump integer msgpack value.
|
|
function s:msgpack_dump_integer(v) abort
|
|
if type(a:v) == type({})
|
|
return msgpack#int_dict_to_str(a:v)
|
|
else
|
|
return string(a:v)
|
|
endif
|
|
endfunction
|
|
|
|
""
|
|
" Dump floating-point value.
|
|
function s:msgpack_dump_float(v) abort
|
|
return substitute(string(type(a:v) == type({}) ? a:v._VAL : a:v),
|
|
\'\V\^\(-\)\?str2float(''\(inf\|nan\)'')\$', '\1\2', '')
|
|
endfunction
|
|
|
|
""
|
|
" Dump |msgpack-special-dict| that represents a string. If any additional
|
|
" parameter is given then it dumps binary string.
|
|
function s:msgpack_dump_string(v) abort
|
|
if type(a:v) == type({})
|
|
let val = a:v
|
|
else
|
|
let val = {'_VAL': split(a:v, "\n", 1)}
|
|
end
|
|
|
|
let ret = ['"']
|
|
for v in val._VAL
|
|
call add(
|
|
\ret,
|
|
\substitute(
|
|
\substitute(v, '["\\]', '\\\0', 'g'),
|
|
\'\n', '\\0', 'g'))
|
|
call add(ret, '\n')
|
|
endfor
|
|
let ret[-1] = '"'
|
|
return join(ret, '')
|
|
endfunction
|
|
|
|
""
|
|
" Dump array value.
|
|
function s:msgpack_dump_array(v) abort
|
|
let val = type(a:v) == type({}) ? a:v._VAL : a:v
|
|
return '[' . join(map(val[:], 'msgpack#string(v:val)'), ', ') . ']'
|
|
endfunction
|
|
|
|
""
|
|
" Dump dictionary value.
|
|
function s:msgpack_dump_map(v) abort
|
|
let ret = ['{']
|
|
if msgpack#special_type(a:v) is 0
|
|
for [k, v] in items(a:v)
|
|
let ret += [s:msgpack_dump_string({'_VAL': split(k, "\n")}),
|
|
\': ',
|
|
\msgpack#string(v),
|
|
\', ']
|
|
unlet v
|
|
endfor
|
|
if !empty(a:v)
|
|
call remove(ret, -1)
|
|
endif
|
|
else
|
|
for [k, v] in sort(copy(a:v._VAL))
|
|
let ret += [msgpack#string(k),
|
|
\': ',
|
|
\msgpack#string(v),
|
|
\', ']
|
|
unlet k
|
|
unlet v
|
|
endfor
|
|
if !empty(a:v._VAL)
|
|
call remove(ret, -1)
|
|
endif
|
|
endif
|
|
let ret += ['}']
|
|
return join(ret, '')
|
|
endfunction
|
|
|
|
""
|
|
" Dump extension value.
|
|
function s:msgpack_dump_ext(v) abort
|
|
return printf('+(%i)%s', a:v._VAL[0],
|
|
\s:msgpack_dump_string({'_VAL': a:v._VAL[1]}))
|
|
endfunction
|
|
|
|
""
|
|
" Convert msgpack object to a string, like string() function does. Result of the
|
|
" conversion may be passed to msgpack#eval().
|
|
function msgpack#string(v) abort
|
|
if type(a:v) == type({})
|
|
let type = msgpack#special_type(a:v)
|
|
if type is 0
|
|
let type = 'map'
|
|
endif
|
|
else
|
|
let type = get(s:MSGPACK_STANDARD_TYPES, type(a:v), 0)
|
|
if type is 0
|
|
throw printf('msgpack:invtype: Unable to convert value %s', string(a:v))
|
|
endif
|
|
endif
|
|
return s:msgpack_dump_{type}(a:v)
|
|
endfunction
|
|
|
|
""
|
|
" Copy msgpack object like deepcopy() does, but leave types intact
|
|
function msgpack#deepcopy(obj) abort
|
|
if type(a:obj) == type([])
|
|
return map(copy(a:obj), 'msgpack#deepcopy(v:val)')
|
|
elseif type(a:obj) == type({})
|
|
let special_type = msgpack#special_type(a:obj)
|
|
if special_type is 0
|
|
return map(copy(a:obj), 'msgpack#deepcopy(v:val)')
|
|
else
|
|
return {
|
|
\'_TYPE': v:msgpack_types[special_type],
|
|
\'_VAL': msgpack#deepcopy(a:obj._VAL)
|
|
\}
|
|
endif
|
|
else
|
|
return copy(a:obj)
|
|
endif
|
|
endfunction
|
|
|
|
""
|
|
" Convert an escaped character to needed value
|
|
function s:msgpack_eval_str_sub(ch) abort
|
|
if a:ch is# 'n'
|
|
return '", "'
|
|
elseif a:ch is# '0'
|
|
return '\n'
|
|
else
|
|
return '\' . a:ch
|
|
endif
|
|
endfunction
|
|
|
|
let s:MSGPACK_SPECIAL_OBJECTS = {
|
|
\'NIL': '{''_TYPE'': v:msgpack_types.nil, ''_VAL'': 0}',
|
|
\'TRUE': '{''_TYPE'': v:msgpack_types.boolean, ''_VAL'': 1}',
|
|
\'FALSE': '{''_TYPE'': v:msgpack_types.boolean, ''_VAL'': 0}',
|
|
\'nan': '(-(1.0/0.0-1.0/0.0))',
|
|
\'inf': '(1.0/0.0)',
|
|
\}
|
|
|
|
""
|
|
" Convert msgpack object dumped by msgpack#string() to a Vimscript object
|
|
" suitable for msgpackdump().
|
|
"
|
|
" @param[in] s String to evaluate.
|
|
" @param[in] special_objs Additional special objects, in the same format as
|
|
" s:MSGPACK_SPECIAL_OBJECTS.
|
|
"
|
|
" @return Any value that msgpackparse() may return.
|
|
function msgpack#eval(s, special_objs) abort
|
|
let s = a:s
|
|
let expr = []
|
|
let context = []
|
|
while !empty(s)
|
|
let s = substitute(s, '^\s*', '', '')
|
|
if s[0] =~# '\v^\h$'
|
|
let name = matchstr(s, '\v\C^\w+')
|
|
if has_key(s:MSGPACK_SPECIAL_OBJECTS, name)
|
|
call add(expr, s:MSGPACK_SPECIAL_OBJECTS[name])
|
|
elseif has_key(a:special_objs, name)
|
|
call add(expr, a:special_objs[name])
|
|
else
|
|
throw 'name-unknown:Unknown name ' . name . ': ' . s
|
|
endif
|
|
let s = s[len(name):]
|
|
elseif (s[0] is# '-' && s[1] =~# '\v^\d$') || s[0] =~# '\v^\d$'
|
|
let sign = 1
|
|
if s[0] is# '-'
|
|
let s = s[1:]
|
|
let sign = -1
|
|
endif
|
|
if s[0:1] is# '0x'
|
|
" See comment in msgpack#int_dict_to_str().
|
|
let s = s[2:]
|
|
let hexnum = matchstr(s, '\v\C^\x+')
|
|
if empty(hexnum)
|
|
throw '0x-empty:Must have number after 0x: ' . s
|
|
elseif len(hexnum) > 16
|
|
throw '0x-long:Must have at most 16 hex digits: ' . s
|
|
endif
|
|
let s = s[len(hexnum):]
|
|
let hexnum = repeat('0', 16 - len(hexnum)) . hexnum
|
|
let g1 = str2nr(hexnum[0], 16)
|
|
let g2 = str2nr(hexnum[1:7], 16)
|
|
let g3 = str2nr(hexnum[8], 16)
|
|
let g4 = str2nr(hexnum[9:15], 16)
|
|
let v1 = s:shift(g1, -2)
|
|
let v2 = or(or(s:shift(s:mask1(g1, 2), 29), s:shift(g2, 1)),
|
|
\s:mask1(s:shift(g3, -3), 1))
|
|
let v3 = or(s:shift(s:mask1(g3, 3), 28), g4)
|
|
call add(expr, printf('{''_TYPE'': v:msgpack_types.integer, '.
|
|
\'''_VAL'': [%i, %u, %u, %u]}',
|
|
\sign, v1, v2, v3))
|
|
else
|
|
let num = matchstr(s, '\v\C^\d+')
|
|
let s = s[len(num):]
|
|
if sign == -1
|
|
call add(expr, '-')
|
|
endif
|
|
call add(expr, num)
|
|
if s[0] is# '.'
|
|
let dec = matchstr(s, '\v\C^\.\d+%(e[+-]?\d+)?')
|
|
if empty(dec)
|
|
throw '0.-nodigits:Decimal dot must be followed by digit(s): ' . s
|
|
endif
|
|
let s = s[len(dec):]
|
|
call add(expr, dec)
|
|
endif
|
|
endif
|
|
elseif s =~# '\v^\-%(inf|nan)'
|
|
call add(expr, '-')
|
|
call add(expr, s:MSGPACK_SPECIAL_OBJECTS[s[1:3]])
|
|
let s = s[4:]
|
|
elseif stridx('="+', s[0]) != -1
|
|
let match = matchlist(s, '\v\C^(\=|\+\((\-?\d+)\)|)(\"%(\\.|[^\\"]+)*\")')
|
|
if empty(match)
|
|
throw '"-invalid:Invalid string: ' . s
|
|
endif
|
|
call add(expr, '{''_TYPE'': v:msgpack_types.')
|
|
if empty(match[1]) || match[1] is# '='
|
|
call add(expr, 'string')
|
|
else
|
|
call add(expr, 'ext')
|
|
endif
|
|
call add(expr, ', ''_VAL'': [')
|
|
if match[1][0] is# '+'
|
|
call add(expr, match[2] . ', [')
|
|
endif
|
|
call add(expr, substitute(match[3], '\v\C\\(.)',
|
|
\'\=s:msgpack_eval_str_sub(submatch(1))', 'g'))
|
|
if match[1][0] is# '+'
|
|
call add(expr, ']')
|
|
endif
|
|
call add(expr, ']}')
|
|
let s = s[len(match[0]):]
|
|
elseif s[0] is# '{'
|
|
call add(context, 'map')
|
|
call add(expr, '{''_TYPE'': v:msgpack_types.map, ''_VAL'': [')
|
|
call add(expr, '[')
|
|
let s = s[1:]
|
|
elseif s[0] is# '['
|
|
call add(context, 'array')
|
|
call add(expr, '[')
|
|
let s = s[1:]
|
|
elseif s[0] is# ':'
|
|
call add(expr, ',')
|
|
let s = s[1:]
|
|
elseif s[0] is# ','
|
|
if context[-1] is# 'array'
|
|
call add(expr, ',')
|
|
else
|
|
call add(expr, '], [')
|
|
endif
|
|
let s = s[1:]
|
|
elseif s[0] is# ']'
|
|
call remove(context, -1)
|
|
call add(expr, ']')
|
|
let s = s[1:]
|
|
elseif s[0] is# '}'
|
|
call remove(context, -1)
|
|
if expr[-1] is# "\x5B"
|
|
call remove(expr, -1)
|
|
else
|
|
call add(expr, ']')
|
|
endif
|
|
call add(expr, ']}')
|
|
let s = s[1:]
|
|
elseif s[0] is# ''''
|
|
let char = matchstr(s, '\v\C^\''\zs%(\\\d+|.)\ze\''')
|
|
if empty(char)
|
|
throw 'char-invalid:Invalid integer character literal format: ' . s
|
|
endif
|
|
if char[0] is# '\'
|
|
call add(expr, +char[1:])
|
|
else
|
|
call add(expr, char2nr(char))
|
|
endif
|
|
let s = s[len(char) + 2:]
|
|
else
|
|
throw 'unknown:Invalid non-space character: ' . s
|
|
endif
|
|
endwhile
|
|
if empty(expr)
|
|
throw 'empty:Parsed string is empty'
|
|
endif
|
|
return eval(join(expr, ''))
|
|
endfunction
|
|
|
|
""
|
|
" Check whether two msgpack values are equal
|
|
function msgpack#equal(a, b)
|
|
let atype = msgpack#type(a:a)
|
|
let btype = msgpack#type(a:b)
|
|
if atype isnot# btype
|
|
return 0
|
|
endif
|
|
let aspecial = msgpack#special_type(a:a)
|
|
let bspecial = msgpack#special_type(a:b)
|
|
if aspecial is# bspecial
|
|
if aspecial is# 0
|
|
if type(a:a) == type({})
|
|
if len(a:a) != len(a:b)
|
|
return 0
|
|
endif
|
|
if !empty(filter(keys(a:a), '!has_key(a:b, v:val)'))
|
|
return 0
|
|
endif
|
|
for [k, v] in items(a:a)
|
|
if !msgpack#equal(v, a:b[k])
|
|
return 0
|
|
endif
|
|
unlet v
|
|
endfor
|
|
return 1
|
|
elseif type(a:a) == type([])
|
|
if len(a:a) != len(a:b)
|
|
return 0
|
|
endif
|
|
let i = 0
|
|
for asubval in a:a
|
|
if !msgpack#equal(asubval, a:b[i])
|
|
return 0
|
|
endif
|
|
let i += 1
|
|
unlet asubval
|
|
endfor
|
|
return 1
|
|
elseif type(a:a) == type(0.0)
|
|
return (a:a == a:a ? a:a == a:b : string(a:a) ==# string(a:b))
|
|
else
|
|
return a:a ==# a:b
|
|
endif
|
|
elseif aspecial is# 'map' || aspecial is# 'array'
|
|
if len(a:a._VAL) != len(a:b._VAL)
|
|
return 0
|
|
endif
|
|
let alist = aspecial is# 'map' ? sort(copy(a:a._VAL)) : a:a._VAL
|
|
let blist = bspecial is# 'map' ? sort(copy(a:b._VAL)) : a:b._VAL
|
|
let i = 0
|
|
for asubval in alist
|
|
let bsubval = blist[i]
|
|
if aspecial is# 'map'
|
|
if !(msgpack#equal(asubval[0], bsubval[0])
|
|
\&& msgpack#equal(asubval[1], bsubval[1]))
|
|
return 0
|
|
endif
|
|
else
|
|
if !msgpack#equal(asubval, bsubval)
|
|
return 0
|
|
endif
|
|
endif
|
|
let i += 1
|
|
unlet asubval
|
|
unlet bsubval
|
|
endfor
|
|
return 1
|
|
elseif aspecial is# 'nil'
|
|
return 1
|
|
elseif aspecial is# 'float'
|
|
return (a:a._VAL == a:a._VAL
|
|
\? (a:a._VAL == a:b._VAL)
|
|
\: (string(a:a._VAL) ==# string(a:b._VAL)))
|
|
else
|
|
return a:a._VAL ==# a:b._VAL
|
|
endif
|
|
else
|
|
if atype is# 'array'
|
|
let a = aspecial is 0 ? a:a : a:a._VAL
|
|
let b = bspecial is 0 ? a:b : a:b._VAL
|
|
return msgpack#equal(a, b)
|
|
elseif atype is# 'string'
|
|
let a = (aspecial is 0 ? split(a:a, "\n", 1) : a:a._VAL)
|
|
let b = (bspecial is 0 ? split(a:b, "\n", 1) : a:b._VAL)
|
|
return a ==# b
|
|
elseif atype is# 'map'
|
|
if aspecial is 0
|
|
let akeys = copy(a:a)
|
|
if len(a:b._VAL) != len(akeys)
|
|
return 0
|
|
endif
|
|
for [k, v] in a:b._VAL
|
|
if msgpack#type(k) isnot# 'string'
|
|
" Non-special mapping cannot have non-string keys
|
|
return 0
|
|
endif
|
|
if type(k) == type({})
|
|
if (empty(k._VAL)
|
|
\|| k._VAL ==# [""]
|
|
\|| !empty(filter(copy(k._VAL), 'stridx(v:val, "\n") != -1')))
|
|
" Non-special mapping cannot have zero byte in key or an empty key
|
|
return 0
|
|
endif
|
|
let kstr = join(k._VAL, "\n")
|
|
else
|
|
let kstr = k
|
|
endif
|
|
if !has_key(akeys, kstr)
|
|
" Protects from both missing and duplicate keys
|
|
return 0
|
|
endif
|
|
if !msgpack#equal(akeys[kstr], v)
|
|
return 0
|
|
endif
|
|
call remove(akeys, kstr)
|
|
unlet k
|
|
unlet v
|
|
endfor
|
|
return 1
|
|
else
|
|
return msgpack#equal(a:b, a:a)
|
|
endif
|
|
elseif atype is# 'float'
|
|
let a = aspecial is 0 ? a:a : a:a._VAL
|
|
let b = bspecial is 0 ? a:b : a:b._VAL
|
|
return (a == a ? a == b : string(a) ==# string(b))
|
|
elseif atype is# 'integer'
|
|
if aspecial is 0
|
|
let sign = a:a >= 0 ? 1 : -1
|
|
let a = sign * a:a
|
|
let v1 = s:mask1(s:shift(a, -62), 2)
|
|
let v2 = s:mask1(s:shift(a, -31), 31)
|
|
let v3 = s:mask1(a, 31)
|
|
return [sign, v1, v2, v3] == a:b._VAL
|
|
else
|
|
return msgpack#equal(a:b, a:a)
|
|
endif
|
|
else
|
|
throw printf('internal-invalid-type: %s == %s, but special %s /= %s',
|
|
\atype, btype, aspecial, bspecial)
|
|
endif
|
|
endif
|
|
endfunction
|