Merge pull request #3270 from ZyX-I/shada-support

Add plugin for editing ShaDa files
This commit is contained in:
Felipe Morales 2015-11-23 00:27:18 +01:00
commit 321db59ca1
10 changed files with 5551 additions and 0 deletions

View File

@ -0,0 +1,820 @@
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 ['', '3']
try
execute 'python' . suf
\. "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 datetime\n"
\. " import vim\n"
\. " fmt = vim.eval('a:format')\n"
\. " timestr = vim.eval('a:string')\n"
\. " timestamp = datetime.datetime.strptime(timestr, fmt)\n"
\. " timestamp = int(timestamp.timestamp())\n"
\. " if timestamp > 2 ** 31:\n"
\. " tsabs = abs(timestamp)"
\. " 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 VimL 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(''): 'binary',
\type([]): 'array',
\type({}): 'map',
\}
""
" 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._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 string(type(a:v) == type({}) ? a:v._VAL : a:v)
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
let ret = [a:0 ? '"' : '="']
for v in a:v._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 binary string.
function s:msgpack_dump_binary(v) abort
if type(a:v) == type({})
return s:msgpack_dump_string(a:v, 1)
else
return s:msgpack_dump_string({'_VAL': split(a:v, "\n", 1)}, 1)
endif
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", 1)}),
\': ',
\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]}, 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 VimL 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 =~# '-\?\%(inf\|nan\)'
if s[0] is# '-'
call add(expr, '-')
let s = s[1:]
endif
call add(expr, s:MSGPACK_SPECIAL_OBJECTS[s[0:2]])
let s = s[3:]
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])
call add(expr, 'binary')
elseif 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, '\m\C^''\zs.\ze''')
if empty(char)
throw 'char-invalid:Invalid integer character literal format: ' . s
endif
call add(expr, char2nr(char))
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# 'binary'
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 (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")
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

694
runtime/autoload/shada.vim Normal file
View File

@ -0,0 +1,694 @@
if exists('g:loaded_shada_autoload')
finish
endif
let g:loaded_shada_autoload = 1
""
" If true keep the old header entry when editing existing ShaDa file.
"
" Old header entry will be kept only if it is listed in the opened file. To
" remove old header entry despite of the setting just remove it from the
" listing. Setting it to false makes plugin ignore all header entries. Defaults
" to 1.
let g:shada#keep_old_header = get(g:, 'shada#keep_old_header', 1)
""
" If true then first entry will be plugins own header entry.
let g:shada#add_own_header = get(g:, 'shada#add_own_header', 1)
""
" Dictionary that maps ShaDa types to their names.
let s:SHADA_ENTRY_NAMES = {
\1: 'header',
\2: 'search_pattern',
\3: 'replacement_string',
\4: 'history_entry',
\5: 'register',
\6: 'variable',
\7: 'global_mark',
\8: 'jump',
\9: 'buffer_list',
\10: 'local_mark',
\11: 'change',
\}
""
" Dictionary that maps ShaDa names to corresponding types
let s:SHADA_ENTRY_TYPES = {}
call map(copy(s:SHADA_ENTRY_NAMES),
\'extend(s:SHADA_ENTRY_TYPES, {v:val : +v:key})')
""
" Map that maps entry names to lists of keys that can be used by this entry.
" Only contains data for entries which are represented as mappings, except for
" the header.
let s:SHADA_MAP_ENTRIES = {
\'search_pattern': ['sp', 'sh', 'ss', 'sm', 'sc', 'sl', 'se', 'so', 'su'],
\'register': ['n', 'rc', 'rw', 'rt'],
\'global_mark': ['n', 'f', 'l', 'c'],
\'local_mark': ['f', 'n', 'l', 'c'],
\'jump': ['f', 'l', 'c'],
\'change': ['f', 'l', 'c'],
\'header': [],
\}
""
" Like one of the values from s:SHADA_MAP_ENTRIES, but for a single buffer in
" buffer list entry.
let s:SHADA_BUFFER_LIST_KEYS = ['f', 'l', 'c']
""
" List of possible history types. Maps integer values that represent history
" types to human-readable names.
let s:SHADA_HISTORY_TYPES = ['command', 'search', 'expression', 'input', 'debug']
""
" Map that maps entry names to their descriptions. Only for entries which have
" list as a data type. Description is a list of lists where each entry has item
" description and item type.
let s:SHADA_FIXED_ARRAY_ENTRIES = {
\'replacement_string': [[':s replacement string', 'bin']],
\'history_entry': [
\['history type', 'histtype'],
\['contents', 'bin'],
\['separator', 'intchar'],
\],
\'variable': [['name', 'bin'], ['value', 'any']],
\}
""
" Dictionary that maps enum names to dictionary with enum values. Dictionary
" with enum values maps enum human-readable names to corresponding values. Enums
" are used as type names in s:SHADA_FIXED_ARRAY_ENTRIES and
" s:SHADA_STANDARD_KEYS.
let s:SHADA_ENUMS = {
\'histtype': {
\'CMD': 0,
\'SEARCH': 1,
\'EXPR': 2,
\'INPUT': 3,
\'DEBUG': 4,
\},
\'regtype': {
\'CHARACTERWISE': 0,
\'LINEWISE': 1,
\'BLOCKWISE': 2,
\}
\}
""
" Second argument to msgpack#eval.
let s:SHADA_SPECIAL_OBJS = {}
call map(values(s:SHADA_ENUMS),
\'extend(s:SHADA_SPECIAL_OBJS, map(copy(v:val), "string(v:val)"))')
""
" Like s:SHADA_ENUMS, but inner dictionary maps values to names and not names to
" values.
let s:SHADA_REV_ENUMS = map(copy(s:SHADA_ENUMS), '{}')
call map(copy(s:SHADA_ENUMS),
\'map(copy(v:val), '
\. '"extend(s:SHADA_REV_ENUMS[" . string(v:key) . "], '
\. '{v:val : v:key})")')
""
" Maximum length of ShaDa entry name. Used to arrange entries to the table.
let s:SHADA_MAX_ENTRY_LENGTH = max(
\map(values(s:SHADA_ENTRY_NAMES), 'len(v:val)')
\+ [len('unknown (0x)') + 16])
""
" Object that marks required value.
let s:SHADA_REQUIRED = []
""
" Dictionary that maps default key names to their description. Description is
" a list that contains human-readable hint, key type and default value.
let s:SHADA_STANDARD_KEYS = {
\'sm': ['magic value', 'boolean', g:msgpack#true],
\'sc': ['smartcase value', 'boolean', g:msgpack#false],
\'sl': ['has line offset', 'boolean', g:msgpack#false],
\'se': ['place cursor at end', 'boolean', g:msgpack#false],
\'so': ['offset value', 'integer', 0],
\'su': ['is last used', 'boolean', g:msgpack#true],
\'ss': ['is :s pattern', 'boolean', g:msgpack#false],
\'sh': ['v:hlsearch value', 'boolean', g:msgpack#false],
\'sp': ['pattern', 'bin', s:SHADA_REQUIRED],
\'rt': ['type', 'regtype', s:SHADA_ENUMS.regtype.CHARACTERWISE],
\'rw': ['block width', 'uint', 0],
\'rc': ['contents', 'binarray', s:SHADA_REQUIRED],
\'n': ['name', 'intchar', char2nr('"')],
\'l': ['line number', 'uint', 1],
\'c': ['column', 'uint', 0],
\'f': ['file name', 'bin', s:SHADA_REQUIRED],
\}
""
" Set of entry types containing entries which require `n` key.
let s:SHADA_REQUIRES_NAME = {'local_mark': 1, 'global_mark': 1, 'register': 1}
""
" Maximum width of human-readable hint. Used to arrange data in table.
let s:SHADA_MAX_HINT_WIDTH = max(map(values(s:SHADA_STANDARD_KEYS),
\'len(v:val[0])'))
""
" Default mark name for the cases when it makes sense (i.e. for local marks).
let s:SHADA_DEFAULT_MARK_NAME = '"'
""
" Mapping that maps timestamps represented using msgpack#string to strftime
" output. Used by s:shada_strftime.
let s:shada_strftime_cache = {}
""
" Mapping that maps strftime output from s:shada_strftime to timestamps.
let s:shada_strptime_cache = {}
""
" Time format used for displaying ShaDa files.
let s:SHADA_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
""
" Wrapper around msgpack#strftime that caches its output.
"
" Format is hardcoded to s:SHADA_TIME_FORMAT.
function s:shada_strftime(timestamp) abort
let key = msgpack#string(a:timestamp)
if has_key(s:shada_strftime_cache, key)
return s:shada_strftime_cache[key]
endif
let val = msgpack#strftime(s:SHADA_TIME_FORMAT, a:timestamp)
let s:shada_strftime_cache[key] = val
let s:shada_strptime_cache[val] = a:timestamp
return val
endfunction
""
" Wrapper around msgpack#strftime that uses cache created by s:shada_strftime().
"
" Also caches its own results. Format is hardcoded to s:SHADA_TIME_FORMAT.
function s:shada_strptime(string) abort
if has_key(s:shada_strptime_cache, a:string)
return s:shada_strptime_cache[a:string]
endif
let ts = msgpack#strptime(s:SHADA_TIME_FORMAT, a:string)
let s:shada_strptime_cache[a:string] = ts
return ts
endfunction
""
" Check whether given value matches given type.
"
" @return Zero if value matches, error message string if it does not.
function s:shada_check_type(type, val) abort
let type = msgpack#type(a:val)
if type is# a:type
return 0
endif
if has_key(s:SHADA_ENUMS, a:type)
let msg = s:shada_check_type('uint', a:val)
if msg isnot 0
return msg
endif
if !has_key(s:SHADA_REV_ENUMS[a:type], a:val)
let evals_msg = join(map(sort(items(s:SHADA_REV_ENUMS[a:type])),
\'v:val[0] . " (" . v:val[1] . ")"'), ', ')
return 'Unexpected enum value: expected one of ' . evals_msg
endif
return 0
elseif a:type is# 'uint'
if type isnot# 'integer'
return 'Expected integer'
endif
if !(type(a:val) == type({}) ? a:val._VAL[0] == 1 : a:val >= 0)
return 'Value is negative'
endif
return 0
elseif a:type is# 'bin'
" Binary string without zero bytes
if type isnot# 'binary'
return 'Expected binary string'
elseif (type(a:val) == type({})
\&& !empty(filter(copy(a:val._VAL), 'stridx(v:val, "\n") != -1')))
return 'Expected no NUL bytes'
endif
return 0
elseif a:type is# 'intchar'
let msg = s:shada_check_type('uint', a:val)
if msg isnot# 0
return msg
endif
if a:val > 0 || a:val < 1
endif
return 0
elseif a:type is# 'binarray'
if type isnot# 'array'
return 'Expected array value'
elseif !empty(filter(copy(type(a:val) == type({}) ? a:val._VAL : a:val),
\'msgpack#type(v:val) isnot# "binary"'))
return 'Expected array of binary strings'
else
for element in (type(a:val) == type({}) ? a:val._VAL : a:val)
if (type(element) == type({})
\&& !empty(filter(copy(element._VAL), 'stridx(v:val, "\n") != -1')))
return 'Expected no NUL bytes'
endif
unlet element
endfor
endif
return 0
elseif a:type is# 'boolean'
return 'Expected boolean'
elseif a:type is# 'integer'
return 'Expected integer'
elseif a:type is# 'any'
return 0
endif
return 'Internal error: unknown type ' . a:type
endfunction
""
" Convert msgpack mapping object to a list of strings for
" s:shada_convert_entry().
"
" @param[in] map Mapping to convert.
" @param[in] default_keys List of keys which have default value in this
" mapping.
" @param[in] name Name of the converted entry.
function s:shada_convert_map(map, default_keys, name) abort
let ret = []
let keys = copy(a:default_keys)
call map(sort(keys(a:map)), 'index(keys, v:val) == -1 ? add(keys, v:val) : 0')
let descriptions = map(copy(keys),
\'get(s:SHADA_STANDARD_KEYS, v:val, ["", 0, 0])')
let max_key_len = max(map(copy(keys), 'len(v:val)'))
let max_desc_len = max(map(copy(descriptions),
\'v:val[0] is 0 ? 0 : len(v:val[0])'))
if max_key_len < len('Key')
let max_key_len = len('Key')
endif
let key_header = 'Key' . repeat('_', max_key_len - len('Key'))
if max_desc_len == 0
call add(ret, printf(' %% %s %s', key_header, 'Value'))
else
if max_desc_len < len('Description')
let max_desc_len = len('Description')
endif
let desc_header = ('Description'
\. repeat('_', max_desc_len - len('Description')))
call add(ret, printf(' %% %s %s %s', key_header, desc_header, 'Value'))
endif
let i = 0
for key in keys
let [description, type, default] = descriptions[i]
if a:name isnot# 'local_mark' && key is# 'n'
unlet default
let default = s:SHADA_REQUIRED
endif
let value = get(a:map, key, default)
if (key is# 'n' && !has_key(s:SHADA_REQUIRES_NAME, a:name)
\&& value is# s:SHADA_REQUIRED)
" Do nothing
elseif value is s:SHADA_REQUIRED
call add(ret, ' # Required key missing: ' . key)
elseif max_desc_len == 0
call add(ret, printf(' + %-*s %s',
\max_key_len, key,
\msgpack#string(value)))
else
if type isnot 0 && value isnot# default
let msg = s:shada_check_type(type, value)
if msg isnot 0
call add(ret, ' # ' . msg)
endif
endif
let strval = s:shada_string(type, value)
if msgpack#type(value) is# 'array' && msg is 0
let shift = 2 + 2 + max_key_len + 2 + max_desc_len + 2
" Value: 1 2 3 4 5 6:
" " + Key Description Value"
" 1122333445555555555566
if shift + strdisplaywidth(strval, shift) > 80
let strval = '@'
endif
endif
call add(ret, printf(' + %-*s %-*s %s',
\max_key_len, key,
\max_desc_len, description,
\strval))
if strval is '@'
for v in value
call add(ret, printf(' | - %s', msgpack#string(v)))
unlet v
endfor
endif
endif
let i += 1
unlet value
unlet default
endfor
return ret
endfunction
""
" Wrapper around msgpack#string() which may return string from s:SHADA_REV_ENUMS
function s:shada_string(type, v) abort
if (has_key(s:SHADA_ENUMS, a:type) && type(a:v) == type(0)
\&& has_key(s:SHADA_REV_ENUMS[a:type], a:v))
return s:SHADA_REV_ENUMS[a:type][a:v]
elseif (a:type is# 'intchar' && type(a:v) == type(0)
\&& strtrans(nr2char(a:v)) is# nr2char(a:v))
return "'" . nr2char(a:v) . "'"
else
return msgpack#string(a:v)
endif
endfunction
""
" Evaluate string obtained by s:shada_string().
function s:shada_eval(s) abort
return msgpack#eval(a:s, s:SHADA_SPECIAL_OBJS)
endfunction
""
" Convert one ShaDa entry to a list of strings suitable for setline().
"
" Returned format looks like this:
"
" TODO
function s:shada_convert_entry(entry) abort
if type(a:entry.type) == type({})
" |msgpack-special-dict| may only be used if value does not fit into the
" default integer type. All known entry types do fit, so it is definitely
" unknown entry.
let name = 'unknown_(' . msgpack#int_dict_to_str(a:entry.type) . ')'
else
let name = get(s:SHADA_ENTRY_NAMES, a:entry.type, 0)
if name is 0
let name = printf('unknown_(0x%x)', a:entry.type)
endif
endif
let title = toupper(name[0]) . tr(name[1:], '_', ' ')
let header = printf('%s with timestamp %s:', title,
\s:shada_strftime(a:entry.timestamp))
let ret = [header]
if name[:8] is# 'unknown_(' && name[-1:] is# ')'
call add(ret, ' = ' . msgpack#string(a:entry.data))
elseif has_key(s:SHADA_FIXED_ARRAY_ENTRIES, name)
if type(a:entry.data) != type([])
call add(ret, printf(' # Unexpected type: %s instead of array',
\msgpack#type(a:entry.data)))
call add(ret, ' = ' . msgpack#string(a:entry.data))
return ret
endif
let i = 0
let max_desc_len = max(map(copy(s:SHADA_FIXED_ARRAY_ENTRIES[name]),
\'len(v:val[0])'))
if max_desc_len < len('Description')
let max_desc_len = len('Description')
endif
let desc_header = ('Description'
\. repeat('_', max_desc_len - len('Description')))
call add(ret, printf(' @ %s %s', desc_header, 'Value'))
for value in a:entry.data
let [desc, type] = get(s:SHADA_FIXED_ARRAY_ENTRIES[name], i, ['', 0])
if (i == 2 && name is# 'history_entry'
\&& a:entry.data[0] isnot# s:SHADA_ENUMS.histtype.SEARCH)
let [desc, type] = ['', 0]
endif
if type isnot 0
let msg = s:shada_check_type(type, value)
if msg isnot 0
call add(ret, ' # ' . msg)
endif
endif
call add(ret, printf(' - %-*s %s', max_desc_len, desc,
\s:shada_string(type, value)))
let i += 1
unlet value
endfor
if (len(a:entry.data) < len(s:SHADA_FIXED_ARRAY_ENTRIES[name])
\&& !(name is# 'history_entry'
\&& len(a:entry.data) == 2
\&& a:entry.data[0] isnot# s:SHADA_ENUMS.histtype.SEARCH))
call add(ret, ' # Expected more elements in list')
endif
elseif has_key(s:SHADA_MAP_ENTRIES, name)
if type(a:entry.data) != type({})
call add(ret, printf(' # Unexpected type: %s instead of map',
\msgpack#type(a:entry.data)))
call add(ret, ' = ' . msgpack#string(a:entry.data))
return ret
endif
if msgpack#special_type(a:entry.data) isnot 0
call add(ret, ' # Entry is a special dict which is unexpected')
call add(ret, ' = ' . msgpack#string(a:entry.data))
return ret
endif
let ret += s:shada_convert_map(a:entry.data, s:SHADA_MAP_ENTRIES[name],
\name)
elseif name is# 'buffer_list'
if type(a:entry.data) != type([])
call add(ret, printf(' # Unexpected type: %s instead of array',
\msgpack#type(a:entry.data)))
call add(ret, ' = ' . msgpack#string(a:entry.data))
return ret
elseif !empty(filter(copy(a:entry.data),
\'type(v:val) != type({}) '
\. '|| msgpack#special_type(v:val) isnot 0'))
call add(ret, ' # Expected array of maps')
call add(ret, ' = ' . msgpack#string(a:entry.data))
return ret
endif
for bufdef in a:entry.data
if bufdef isnot a:entry.data[0]
call add(ret, '')
endif
let ret += s:shada_convert_map(bufdef, s:SHADA_BUFFER_LIST_KEYS, name)
endfor
else
throw 'internal-unknown-type:Internal error: unknown type name: ' . name
endif
return ret
endfunction
""
" Order of msgpack objects in one ShaDa entry. Each item in the list is name of
" the key in dictionaries returned by shada#read().
let s:SHADA_ENTRY_OBJECT_SEQUENCE = ['type', 'timestamp', 'length', 'data']
""
" Convert list returned by msgpackparse() to a list of ShaDa objects
"
" @param[in] mpack List of VimL objects returned by msgpackparse().
"
" @return List of dictionaries with keys type, timestamp, length and data. Each
" dictionary describes one ShaDa entry.
function shada#mpack_to_sd(mpack) abort
let ret = []
let i = 0
for element in a:mpack
let key = s:SHADA_ENTRY_OBJECT_SEQUENCE[
\i % len(s:SHADA_ENTRY_OBJECT_SEQUENCE)]
if key is# 'type'
call add(ret, {})
endif
let ret[-1][key] = element
if key isnot# 'data'
if !msgpack#is_uint(element)
throw printf('not-uint:Entry %i has %s element '.
\'which is not an unsigned integer',
\len(ret), key)
endif
if key is# 'type' && msgpack#equal(element, 0)
throw printf('zero-uint:Entry %i has %s element '.
\'which is zero',
\len(ret), key)
endif
endif
let i += 1
unlet element
endfor
return ret
endfunction
""
" Convert read ShaDa file to a list of lines suitable for setline()
"
" @param[in] shada List of ShaDa entries like returned by shada#mpack_to_sd().
"
" @return List of strings suitable for setline()-like functions.
function shada#sd_to_strings(shada) abort
let ret = []
for entry in a:shada
let ret += s:shada_convert_entry(entry)
endfor
return ret
endfunction
""
" Convert a readfile()-like list of strings to a list of lines suitable for
" setline().
"
" @param[in] binstrings List of strings to convert.
"
" @return List of lines.
function shada#get_strings(binstrings) abort
return shada#sd_to_strings(shada#mpack_to_sd(msgpackparse(a:binstrings)))
endfunction
""
" Convert s:shada_convert_entry() output to original entry.
function s:shada_convert_strings(strings) abort
let strings = copy(a:strings)
let match = matchlist(
\strings[0],
\'\v\C^(.{-})\m with timestamp \(\d\{4}-\d\d-\d\dT\d\d:\d\d:\d\d\):$')
if empty(match)
throw 'invalid-header:Header has invalid format: ' . strings[0]
endif
call remove(strings, 0)
let title = match[1]
let name = tolower(title[0]) . tr(title[1:], ' ', '_')
let ret = {}
let empty_default = g:msgpack#nil
if name[:8] is# 'unknown_(' && name[-1:] is# ')'
let ret.type = +name[9:-2]
elseif has_key(s:SHADA_ENTRY_TYPES, name)
let ret.type = s:SHADA_ENTRY_TYPES[name]
if has_key(s:SHADA_MAP_ENTRIES, name)
unlet empty_default
let empty_default = {}
elseif has_key(s:SHADA_FIXED_ARRAY_ENTRIES, name) || name is# 'buffer_list'
unlet empty_default
let empty_default = []
endif
else
throw 'invalid-type:Unknown type ' . name
endif
let ret.timestamp = s:shada_strptime(match[2])
if empty(strings)
let ret.data = empty_default
else
while !empty(strings)
if strings[0][2] is# '='
let data = s:shada_eval(strings[0][4:])
call remove(strings, 0)
elseif strings[0][2] is# '%'
if name is# 'buffer_list' && !has_key(ret, 'data')
let ret.data = []
endif
let match = matchlist(
\strings[0],
\'\m\C^ % \(Key_*\)\( Description_*\)\? Value')
if empty(match)
throw 'invalid-map-header:Invalid mapping header: ' . strings[0]
endif
call remove(strings, 0)
let key_len = len(match[1])
let desc_skip_len = len(match[2])
let data = {'_TYPE': v:msgpack_types.map, '_VAL': []}
while !empty(strings) && strings[0][2] is# '+'
let line = remove(strings, 0)[4:]
let key = substitute(line[:key_len - 1], '\v\C\ *$', '', '')
let strval = line[key_len + desc_skip_len + 2:]
if strval is# '@'
let val = []
while !empty(strings) && strings[0][2] is# '|'
if strings[0][4] isnot# '-'
throw ('invalid-array:Expected hyphen-minus at column 5: '
\. strings)
endif
call add(val, s:shada_eval(remove(strings, 0)[5:]))
endwhile
else
let val = s:shada_eval(strval)
endif
if (has_key(s:SHADA_STANDARD_KEYS, key)
\&& s:SHADA_STANDARD_KEYS[key][2] isnot# s:SHADA_REQUIRED
\&& msgpack#equal(s:SHADA_STANDARD_KEYS[key][2], val))
unlet val
continue
endif
call add(data._VAL, [{'_TYPE': v:msgpack_types.string, '_VAL': [key]},
\val])
unlet val
endwhile
elseif strings[0][2] is# '@'
let match = matchlist(
\strings[0],
\'\m\C^ @ \(Description_* \)\?Value')
if empty(match)
throw 'invalid-array-header:Invalid array header: ' . strings[0]
endif
call remove(strings, 0)
let desc_skip_len = len(match[1])
let data = []
while !empty(strings) && strings[0][2] is# '-'
let val = remove(strings, 0)[4 + desc_skip_len :]
call add(data, s:shada_eval(val))
endwhile
else
throw 'invalid-line:Unrecognized line: ' . strings[0]
endif
if !has_key(ret, 'data')
let ret.data = data
elseif type(ret.data) == type([])
call add(ret.data, data)
else
let ret.data = [ret.data, data]
endif
unlet data
endwhile
endif
let ret._data = msgpackdump([ret.data])
let ret.length = len(ret._data) - 1
for s in ret._data
let ret.length += len(s)
endfor
return ret
endfunction
""
" Convert s:shada_sd_to_strings() output to a list of original entries.
function shada#strings_to_sd(strings) abort
let strings = filter(copy(a:strings), 'v:val !~# ''\v^\s*%(\#|$)''')
let stringss = []
for string in strings
if string[0] isnot# ' '
call add(stringss, [])
endif
call add(stringss[-1], string)
endfor
return map(copy(stringss), 's:shada_convert_strings(v:val)')
endfunction
""
" Convert a list of strings to list of strings suitable for writefile().
function shada#get_binstrings(strings) abort
let entries = shada#strings_to_sd(a:strings)
if !g:shada#keep_old_header
call filter(entries, 'v:val.type != ' . s:SHADA_ENTRY_TYPES.header)
endif
if g:shada#add_own_header
let data = {'version': v:version, 'generator': 'shada.vim'}
let dumped_data = msgpackdump([data])
let length = len(dumped_data) - 1
for s in dumped_data
let length += len(s)
endfor
call insert(entries, {
\'type': s:SHADA_ENTRY_TYPES.header,
\'timestamp': localtime(),
\'length': length,
\'data': data,
\'_data': dumped_data,
\})
endif
let mpack = []
for entry in entries
let mpack += map(copy(s:SHADA_ENTRY_OBJECT_SEQUENCE), 'entry[v:val]')
endfor
return msgpackdump(mpack)
endfunction

View File

@ -557,6 +557,149 @@ Since the text for this plugin is rather long it has been put in a separate
file: |pi_spec.txt|.
SHADA *ft-shada*
Allows editing binary |shada-file|s in a nice way. Opened binary files are
displayed in the following format: >
Type with timestamp YYYY-mm-ddTHH:MM:SS:
% Key__ Description___ Value
+ fooba foo bar baz fo {msgpack-value}
+ abcde abc def ghi jk {msgpack-value}
Other type with timestamp YYYY-mm-ddTHH:MM:SS:
@ Description__ Value
- foo bar baz t {msgpack-value}
# Expected more elements in list
Some other type with timestamp YYYY-mm-ddTHH:MM:SS:
# Unexpected type: type instead of map
= {msgpack-value}
Filetype plugin defines all |Cmd-event|s. Defined |SourceCmd| event makes
"source file.shada" be equivalent to "|:rshada| file.shada". |BufWriteCmd|,
|FileWriteCmd| and |FileAppendCmd| events are affected by the following
settings:
*g:shada#keep_old_header* Boolean, if set to false all header entries
are ignored when writing. Defaults to 1.
*g:shada#add_own_header* Boolean, if set to true first written entry
will always be header entry with two values in
a map with attached data: |v:version| attached
to "version" key and "shada.vim" attached to
"generator" key. Defaults to 1.
Format description:
1. `#` starts a comment. Lines starting with space characters and then `#`
are ignored. Plugin may only add comment lines to indicate some errors in
ShaDa format. Lines containing no non-whitespace characters are also
ignored.
2. Each entry starts with line that has format "{type} with timestamp
{timestamp}:". {timestamp} is |strftime()|-formatted string representing
actual UNIX timestamp value. First strftime() argument is equal to
`%Y-%m-%dT%H:%M:%S`. When writing this timestamp is parsed using
|msgpack#strptime()|, with caching (it remembers which timestamp produced
particular strftime() output and uses this value if you did not change
timestamp). {type} is one of
1 - Header
2 - Search pattern
3 - Replacement string
4 - History entry
5 - Register
6 - Variable
7 - Global mark
8 - Jump
9 - Buffer list
10 - Local mark
11 - Change
* - Unknown (0x{type-hex})
Each type may be represented using Unknown entry: "Jump with timestamp ..."
is the same as "Unknown (0x8) with timestamp ....".
3. After header there is one of the following lines:
1. " % Key__ Description__ Value": map header. After mapping header
follows a table which may contain comments and lines consisting of
" +", key, description and |{msgpack-value}|. Key is separated by at
least two spaces with description, description is separated by at least
two spaces with the value. Each key in the map must be at most as wide
as "Key__" header: "Key" allows at most 3-byte keys, "Key__" allows at
most 5-byte keys. If keys actually occupy less bytes then the rest is
filled with spaces. Keys cannot be empty, end with spaces, contain two
consequent spaces inside of them or contain multibyte characters (use
"=" format if you need this). Descriptions have the same restrictions
on width and contents, except that empty descriptions are allowed.
Description column may be omitted.
When writing description is ignored. Keys with values |msgpack#equal|
to default ones are ignored. Order of keys is preserved. All keys are
treated as strings (not binary strings).
Note: specifically for buffer list entries it is allowed to have more
then one map header. Each map header starts a new map entry inside
buffer list because ShaDa buffer list entry is an array of maps. I.e. >
Buffer list with timestamp 1970-01-01T00:00:00:
% Key Description Value
+ f file name "/foo"
+ l line number 1
+ c column 10
<
is equivalent to >
Buffer list with timestamp 1970-01-01T00:00:00:
= [{="f": "/foo", ="c": 10}]
<
and >
Buffer list with timestamp 1970-01-01T00:00:00:
% Key Description Value
+ f file name "/foo"
% Key Description Value
+ f file name "/bar"
<
is equivalent to >
Buffer list with timestamp 1970-01-01T00:00:00:
= [{="f": "/foo"}, {="f": "/bar"}]
<
Note 2: specifically for register entries special syntax for arrays was
designed: >
Register with timestamp 1970-01-01T00:00:00:
% Key Description Value
+ rc contents @
| - "line1"
| - "line2"
<
This is equivalent to >
Register with timestamp 1970-01-01T00:00:00:
% Key Description Value
+ rc contents ["line1", "line2"]
<
Such syntax is automatically used if array representation appears to be
too lengthy.
2. " @ Description__ Value": array header. Same as map, but key is
omitted and description cannot be omitted. Array entries start with
" -". Example: >
History entry with timestamp 1970-01-01T00:00:00:
@ Description_ Value
- history type SEARCH
- contents "foo"
- separator '/'
<
is equivalent to >
History entry with timestamp 1970-01-01T00:00:00:
= [SEARCH, "foo", '/']
<
Note: special array syntax for register entries is not recognized here.
3. " = {msgpack-value}": raw values. |{msgpack-value}| in this case may
have absolutely any type. Special array syntax for register entries is
not recognized here as well.
SQL *ft-sql*
Since the text for this plugin is rather long it has been put in a separate

139
runtime/doc/pi_msgpack.txt Normal file
View File

@ -0,0 +1,139 @@
*pi_msgpack.txt* For NeoVim version 0.1.
Author: Nikolay Pavlov <kp-pav@yandex.ru>
Copyright: (c) 2015 by Nikolay Pavlov
The Apache license applies to the files in this package, including
runtime/autoload/msgpack.vim, runtime/doc/pi_msgpack.txt and
test/functional/plugin/msgpack_spec.lua. Like anything else that's free,
msgpack.vim and its associated files are provided *as is* and comes with no
warranty of any kind, either expressed or implied. No guarantees of
merchantability. No guarantees of suitability for any purpose. By using this
plugin, you agree that in no event will the copyright holder be liable for any
damages resulting from the use of this software. Use at your own risk!
==============================================================================
1. Contents *msgpack.vim-contents*
1. Contents..............................: |msgpack.vim-contents|
2. Msgpack.vim introduction..............: |msgpack.vim-intro|
3. Msgpack.vim manual....................: |msgpack.vim-manual|
Function arguments....................: |msgpack.vim-arguments|
msgpack#is_int function...............: |msgpack#is_int()|
msgpack#is_uint function..............: |msgpack#is_uint()|
msgpack#strftime function.............: |msgpack#strftime()|
msgpack#strptime function.............: |msgpack#strptime()|
msgpack#int_dict_to_str function......: |msgpack#int_dict_to_str()|
msgpack#special_type function.........: |msgpack#special_type()|
msgpack#type function.................: |msgpack#type()|
msgpack#deepcopy function.............: |msgpack#deepcopy()|
msgpack#string function...............: |msgpack#string()|
msgpack#eval function.................: |msgpack#eval()|
msgpack#equal function................: |msgpack#equal()|
==============================================================================
2. Msgpack.vim introduction *msgpack.vim-intro*
This plugin contains utility functions to be used in conjunction with
|msgpackdump()| and |msgpackparse()| functions.
==============================================================================
3. Msgpack.vim manual *msgpack.vim-manual*
FUNCTION ARGUMENTS *msgpack.vim-arguments*
Disambiguation of arguments described below. Note: if e.g. function is listed
as accepting |{msgpack-integer}| (or anything else) it means that function
does not check whether argument matches its description.
*{msgpack-value}* Either |msgpack-special-dict| or a regular value, but
not function reference.
*{msgpack-integer}* Any value for which |msgpack#type| will return
"integer".
*{msgpack-special-int}* |msgpack-special-dict| representing integer.
msgpack#is_int({msgpack-value}) *msgpack#is_int()*
Returns 1 if given {msgpack-value} is integer value, 0 otherwise.
msgpack#is_uint({msgpack-value}) *msgpack#is_uint()*
Returns 1 if given {msgpack-value} is integer value greater or equal
to zero, 0 otherwise.
*msgpack#strftime*
msgpack#strftime({format}, {msgpack-integer}) *msgpack#strftime()*
Same as |strftime()|, but second argument may be
|msgpack-special-dict|. Requires |+python| or |+python3| to really
work with |msgpack-special-dict|s.
*msgpack#strptime*
msgpack#strptime({format}, {time}) *msgpack#strptime()*
Reverse of |msgpack#strptime()|: for any time and format
|msgpack#equal|( |msgpack#strptime|(format, |msgpack#strftime|(format,
time)), time) be true. Requires |+python| or |+python3|, without it
only supports non-|msgpack-special-dict| nonnegative times and format
equal to `%Y-%m-%dT%H:%M:%S`.
msgpack#int_dict_to_str({msgpack-special-int}) *msgpack#int_dict_to_str()*
Function which converts |msgpack-special-dict| integer value to
a hexadecimal value like 0x1234567890ABCDEF (always returns exactly 16
hexadecimal digits).
msgpack#special_type({msgpack-value}) *msgpack#special_type()*
Returns zero if {msgpack-value} is not |msgpack-special-dict|. If it
is it returns name of the key in |v:msgpack_types| which represents
{msgpack-value} type.
msgpack#type({msgpack-value}) *msgpack#type()*
Returns name of the key in |v:msgpack_types| that represents
{msgpack-value} type. Never returns zero: this function returns
msgpack type which will be dumped by |msgpackdump()| should it receive
a list with singe {msgpack-value} as input.
msgpack#deepcopy({msgpack-value}) *msgpack#deepcopy()*
Like |deepcopy()|, but works correctly with |msgpack-special-dict|
values. Plain |deepcopy()| will destroy all types in
|msgpack-special-dict| values because it will copy _TYPE key values,
while they should be preserved.
msgpack#string({msgpack-value}) *msgpack#string()*
Like |string()|, but saves information about msgpack types. Values
dumped by msgpack#string may be read back by |msgpack#eval()|.
Returns is the following:
- Dictionaries are dumped as "{key1: value1, key2: value2}". Note:
msgpack allows any values in keys, so with some
|msgpack-special-dict| values |msgpack#string()| may produce even
"{{1: 2}: 3, [4]: 5}".
- Lists are dumped as "[value1, value2]".
- Strings are dumped as
1. `"abc"`: binary string.
2. `="abc"`: string.
3. `+(10)"ext"`: extension strings (10 may be replaced with any
8-bit signed integer).
Inside strings the following escape sequences may be present: "\0"
(represents NUL byte), "\n" (represents line feed) and "\""
(represents double quote).
- Floating-point and integer values are dumped using |string()| or
|msgpack#int_dict_to_str()|.
- Booleans are dumped as "TRUE" or "FALSE".
- Nil values are dumped as "NIL".
msgpack#eval({string}, {dict}) *msgpack#eval()*
Transforms string created by |msgpack#string()| into a value suitable
for |msgpackdump()|. Second argument allows adding special values
that start with head characters (|/\h|) and contain only word
characters (|/\w|). Built-in special values are "TRUE", "FALSE",
"NIL", "nan" and "inf" and they cannot be overridden. Map values are
always evaluated to |msgpack-special-dict| values, as well as
hexadecimal digits. When evaluating maps order of keys is preserved.
*msgpack#equal*
msgpack#equal({msgpack-value}, {msgpack-value}) *msgpack#equal()*
Returns 1 if given values are equal, 0 otherwise. When comparing
msgpack map values order of keys is ignored. Comparing
|msgpack-special-dict| with equivalent non-special-dict value
evaluates to 1.
==============================================================================
vim:tw=78:ts=8:ft=help:fdm=marker

View File

@ -0,0 +1,18 @@
if exists('b:did_ftplugin')
finish
endif
let b:did_ftplugin = 1
function! ShaDaIndent(lnum)
if a:lnum == 1 || getline(a:lnum) =~# '\mwith timestamp.*:$'
return 0
else
return shiftwidth()
endif
endfunction
setlocal expandtab tabstop=2 softtabstop=2 shiftwidth=2
setlocal indentexpr=ShaDaIndent(v:lnum) indentkeys=<:>,o,O
let b:undo_ftplugin = 'setlocal et< ts< sts< sw< indentexpr< indentkeys<'

39
runtime/plugin/shada.vim Normal file
View File

@ -0,0 +1,39 @@
if exists('g:loaded_shada_plugin')
finish
endif
let g:loaded_shada_plugin = 1
augroup ShaDaCommands
autocmd!
autocmd BufReadCmd *.shada,*.shada.tmp.[a-z]
\ :if !empty(v:cmdarg)|throw '++opt not supported'|endif
\ |call setline('.', shada#get_strings(readfile(expand('<afile>'),'b')))
\ |setlocal filetype=shada
autocmd FileReadCmd *.shada,*.shada.tmp.[a-z]
\ :if !empty(v:cmdarg)|throw '++opt not supported'|endif
\ |call append("'[", shada#get_strings(readfile(expand('<afile>'), 'b')))
autocmd BufWriteCmd *.shada,*.shada.tmp.[a-z]
\ :if !empty(v:cmdarg)|throw '++opt not supported'|endif
\ |if writefile(shada#get_binstrings(getline(1, '$')),
\expand('<afile>'), 'b') == 0
\ | let &l:modified = (expand('<afile>') is# bufname(+expand('<abuf>'))
\? 0
\: stridx(&cpoptions, '+') != -1)
\ |endif
autocmd FileWriteCmd *.shada,*.shada.tmp.[a-z]
\ :if !empty(v:cmdarg)|throw '++opt not supported'|endif
\ |call writefile(
\shada#get_binstrings(getline(min([line("'["), line("']")]),
\max([line("'["), line("']")]))),
\expand('<afile>'),
\'b')
autocmd FileAppendCmd *.shada,*.shada.tmp.[a-z]
\ :if !empty(v:cmdarg)|throw '++opt not supported'|endif
\ |call writefile(
\shada#get_binstrings(getline(min([line("'["), line("']")]),
\max([line("'["), line("']")]))),
\expand('<afile>'),
\'ab')
autocmd SourceCmd *.shada,*.shada.tmp.[a-z]
\ :execute 'rshada' fnameescape(expand('<afile>'))
augroup END

125
runtime/syntax/shada.vim Normal file
View File

@ -0,0 +1,125 @@
if exists("b:current_syntax")
finish
endif
syntax match ShaDaEntryHeader
\ '^\u.\{-} with timestamp \d\{4}-\d\d-\d\dT\d\d:\d\d:\d\d:$'
syntax match ShaDaEntryName '^\u.\{-}\ze with' contained
\ containedin=ShaDaEntryHeader
syntax match ShaDaEntryTimestamp 'timestamp \zs\d\{4}-\d\d-\d\dT\d\d:\d\d:\d\d'
\ contained containedin=ShaDaEntryHeader
syntax match ShaDaEntryTimestampNumber '\d\+' contained
\ containedin=ShaDaEntryTimestamp
syntax match ShaDaComment '^\s*#.*$'
syntax region ShaDaEntryMapLong start='^ % Key_* Description_* Value$'
\ end='^ %\|^\S'me=s-1 contains=ShaDaComment,ShaDaEntryMapLongEntryStart
syntax region ShaDaEntryMapShort start='^ % Key_* Value$'
\ end='^ %\|^\S'me=s-1 contains=ShaDaComment,ShaDaEntryMapShortEntryStart
syntax match ShaDaEntryMapHeader '^ % Key_* \(Description_* \)\?Value$'
\ contained containedin=ShaDaEntryMapLong,ShaDaEntryMapShort
syntax match ShaDaEntryMapLongEntryStart '^ + 'hs=e-2,he=e-1
\ nextgroup=ShaDaEntryMapLongKey
syntax match ShaDaEntryMapLongKey '\S\+ \+\ze\S'he=e-2 contained
\ nextgroup=ShaDaEntryMapLongDescription
syntax match ShaDaEntryMapLongDescription '.\{-} \ze\S'he=e-2 contained
\ nextgroup=@ShaDaEntryMsgpackValue
syntax match ShaDaEntryMapShortEntryStart '^ + 'hs=e-2,he=e-1 contained
\ nextgroup=ShaDaEntryMapShortKey
syntax match ShaDaEntryMapShortKey '\S\+ \+\ze\S'he=e-2 contained
\ nextgroup=@ShaDaEntryMsgpackValue
syntax match ShaDaEntryMapBinArrayStart '^ | - 'hs=e-4,he=e-1 contained
\ containedin=ShaDaEntryMapLong,ShaDaEntryMapShort
\ nextgroup=@ShaDaEntryMsgpackValue
syntax region ShaDaEntryArray start='^ @ Description_* Value$'
\ end='^\S'me=s-1 keepend
\ contains=ShaDaComment,ShaDaEntryArrayEntryStart,ShaDaEntryArrayHeader
syntax match ShaDaEntryArrayHeader '^ @ Description_* Value$' contained
syntax match ShaDaEntryArrayEntryStart '^ - 'hs=e-2,he=e-1
\ nextgroup=ShaDaEntryArrayDescription
syntax match ShaDaEntryArrayDescription '.\{-} \ze\S'he=e-2 contained
\ nextgroup=@ShaDaEntryMsgpackValue
syntax match ShaDaEntryRawMsgpack '^ = ' nextgroup=@ShaDaEntryMsgpackValue
syntax cluster ShaDaEntryMsgpackValue
\ add=ShaDaMsgpackKeyword,ShaDaMsgpackShaDaKeyword
\ add=ShaDaMsgpackInteger,ShaDaMsgpackCharacter,ShaDaMsgpackFloat
\ add=ShaDaMsgpackBinaryString,ShaDaMsgpackString,ShaDaMsgpackExt
\ add=ShaDaMsgpackArray,ShaDaMsgpackMap
\ add=ShaDaMsgpackMultilineArray
syntax keyword ShaDaMsgpackKeyword contained NIL TRUE FALSE
syntax keyword ShaDaMsgpackShaDaKeyword contained
\ CMD SEARCH EXPR INPUT DEBUG
\ CHARACTERWISE LINEWISE BLOCKWISE
syntax region ShaDaMsgpackBinaryString matchgroup=ShaDaMsgpackStringQuotes
\ start='"' skip='\\"' end='"' contained keepend
syntax match ShaDaMsgpackBinaryStringEscape '\\[\\0n"]'
\ contained containedin=ShaDaMsgpackBinaryString
syntax match ShaDaMsgpackString '=' contained nextgroup=ShaDaMsgpackBinaryString
syntax match ShaDaMsgpackExt '+(-\?\d\+)' contained
\ nextgroup=ShaDaMsgpackBinaryString
syntax match ShaDaMsgpackExtType '-\?\d\+' contained containedin=ShaDaMsgpackExt
syntax match ShaDaMsgpackCharacter /'.'/ contained
syntax match ShaDaMsgpackInteger '-\?\%(0x\x\{,16}\|\d\+\)' contained
syntax match ShaDaMsgpackFloat '-\?\d\+\.\d\+\%(e[+-]\?\d\+\)\?' contained
syntax region ShaDaMsgpackArray matchgroup=ShaDaMsgpackArrayBraces
\ start='\[' end='\]' contained
\ contains=@ShaDaEntryMsgpackValue,ShaDaMsgpackComma
syntax region ShaDaMsgpackMap matchgroup=ShaDaMsgpackMapBraces
\ start='{' end='}' contained
\ contains=@ShaDaEntryMsgpackValue,ShaDaMsgpackComma,ShaDaMsgpackColon
syntax match ShaDaMsgpackComma ',' contained
syntax match ShaDaMsgpackColon ':' contained
syntax match ShaDaMsgpackMultilineArray '@' contained
hi def link ShaDaComment Comment
hi def link ShaDaEntryNumber Number
hi def link ShaDaEntryTimestamp Operator
hi def link ShaDaEntryName Keyword
hi def link ShaDaEntryMapHeader PreProc
hi def link ShaDaEntryMapEntryStart Label
hi def link ShaDaEntryMapLongEntryStart ShaDaEntryMapEntryStart
hi def link ShaDaEntryMapShortEntryStart ShaDaEntryMapEntryStart
hi def link ShaDaEntryMapBinArrayStart ShaDaEntryMapEntryStart
hi def link ShaDaEntryArrayEntryStart ShaDaEntryMapEntryStart
hi def link ShaDaEntryMapKey String
hi def link ShaDaEntryMapLongKey ShaDaEntryMapKey
hi def link ShaDaEntryMapShortKey ShaDaEntryMapKey
hi def link ShaDaEntryMapDescription Comment
hi def link ShaDaEntryMapLongDescription ShaDaEntryMapDescription
hi def link ShaDaEntryMapShortDescription ShaDaEntryMapDescription
hi def link ShaDaEntryArrayHeader PreProc
hi def link ShaDaEntryArrayDescription ShaDaEntryMapDescription
hi def link ShaDaMsgpackKeyword Keyword
hi def link ShaDaMsgpackShaDaKeyword ShaDaMsgpackKeyword
hi def link ShaDaMsgpackCharacter Character
hi def link ShaDaMsgpackInteger Number
hi def link ShaDaMsgpackFloat Float
hi def link ShaDaMsgpackBinaryString String
hi def link ShaDaMsgpackBinaryStringEscape SpecialChar
hi def link ShaDaMsgpackExtType Typedef
hi def link ShaDaMsgpackStringQuotes Operator
hi def link ShaDaMsgpackString ShaDaMsgpackStringQuotes
hi def link ShaDaMsgpackExt ShaDaMsgpackStringQuotes
hi def link ShaDaMsgpackMapBraces Operator
hi def link ShaDaMsgpackArrayBraces ShaDaMsgpackMapBraces
hi def link ShaDaMsgpackComma Operator
hi def link ShaDaMsgpackColon ShaDaMsgpackComma
hi def link ShaDaMsgpackMultilineArray Operator
let b:current_syntax = "shada"

View File

@ -0,0 +1,41 @@
local paths = require('test.config.paths')
local helpers = require('test.functional.helpers')
local spawn, set_session, nvim_prog, merge_args =
helpers.spawn, helpers.set_session, helpers.nvim_prog, helpers.merge_args
local additional_cmd = ''
local function nvim_argv(shada_file)
local rtp_value = ('\'%s/runtime\''):format(
paths.test_source_path:gsub('\'', '\'\''))
local nvim_argv = {nvim_prog, '-u', 'NORC', '-i', shada_file or 'NONE', '-N',
'--cmd', 'set shortmess+=I background=light noswapfile',
'--cmd', 'let &runtimepath=' .. rtp_value,
'--cmd', additional_cmd,
'--embed'}
if helpers.prepend_argv then
return merge_args(helpers.prepend_argv, nvim_argv)
else
return nvim_argv
end
end
local session = nil
local reset = function(...)
if session then
session:exit(0)
end
session = spawn(nvim_argv(...))
set_session(session)
end
local set_additional_cmd = function(s)
additional_cmd = s
end
return {
reset=reset,
set_additional_cmd=set_additional_cmd,
}

View File

@ -0,0 +1,699 @@
local helpers = require('test.functional.helpers')
local eq, nvim_eval, nvim_command, exc_exec =
helpers.eq, helpers.eval, helpers.command, helpers.exc_exec
local plugin_helpers = require('test.functional.plugin.helpers')
local reset = plugin_helpers.reset
describe('In autoload/msgpack.vim', function()
before_each(reset)
local sp = function(typ, val)
return ('{"_TYPE": v:msgpack_types.%s, "_VAL": %s}'):format(typ, val)
end
local mapsp = function(...)
local val = ''
for i=1,(select('#', ...)/2) do
val = ('%s[%s,%s],'):format(val, select(i * 2 - 1, ...),
select(i * 2, ...))
end
return sp('map', '[' .. val .. ']')
end
local nan = -(1.0/0.0-1.0/0.0)
local minus_nan = 1.0/0.0-1.0/0.0
local inf = 1.0/0.0
local minus_inf = -(1.0/0.0)
local has_minus_nan = tostring(nan) ~= tostring(minus_nan)
describe('function msgpack#equal', function()
local msgpack_eq = function(expected, a, b)
eq(expected, nvim_eval(('msgpack#equal(%s, %s)'):format(a, b)))
if a ~= b then
eq(expected, nvim_eval(('msgpack#equal(%s, %s)'):format(b, a)))
end
end
it('compares raw integers correctly', function()
msgpack_eq(1, '1', '1')
msgpack_eq(0, '1', '0')
end)
it('compares integer specials correctly', function()
msgpack_eq(1, sp('integer', '[-1, 1, 0, 0]'),
sp('integer', '[-1, 1, 0, 0]'))
msgpack_eq(0, sp('integer', '[-1, 1, 0, 0]'),
sp('integer', '[ 1, 1, 0, 0]'))
end)
it('compares integer specials with raw integer correctly', function()
msgpack_eq(1, sp('integer', '[-1, 0, 0, 1]'), '-1')
msgpack_eq(0, sp('integer', '[-1, 0, 0, 1]'), '1')
msgpack_eq(0, sp('integer', '[ 1, 0, 0, 1]'), '-1')
msgpack_eq(1, sp('integer', '[ 1, 0, 0, 1]'), '1')
end)
it('compares integer with float correctly', function()
msgpack_eq(0, '0', '0.0')
end)
it('compares raw binaries correctly', function()
msgpack_eq(1, '"abc\\ndef"', '"abc\\ndef"')
msgpack_eq(0, '"abc\\ndef"', '"abc\\nghi"')
end)
it('compares binary specials correctly', function()
msgpack_eq(1, sp('binary', '["abc\\n", "def"]'),
sp('binary', '["abc\\n", "def"]'))
msgpack_eq(0, sp('binary', '["abc", "def"]'),
sp('binary', '["abc\\n", "def"]'))
end)
it('compares binary specials with raw binaries correctly', function()
msgpack_eq(1, sp('binary', '["abc", "def"]'), '"abc\\ndef"')
msgpack_eq(0, sp('binary', '["abc", "def"]'), '"abcdef"')
end)
it('compares string specials correctly', function()
msgpack_eq(1, sp('string', '["abc\\n", "def"]'),
sp('string', '["abc\\n", "def"]'))
msgpack_eq(0, sp('string', '["abc", "def"]'),
sp('string', '["abc\\n", "def"]'))
end)
it('compares string specials with binary correctly', function()
msgpack_eq(0, sp('string', '["abc\\n", "def"]'),
sp('binary', '["abc\\n", "def"]'))
msgpack_eq(0, sp('string', '["abc", "def"]'), '"abc\\ndef"')
msgpack_eq(0, sp('binary', '["abc\\n", "def"]'),
sp('string', '["abc\\n", "def"]'))
msgpack_eq(0, '"abc\\ndef"', sp('string', '["abc", "def"]'))
end)
it('compares ext specials correctly', function()
msgpack_eq(1, sp('ext', '[1, ["", "ac"]]'), sp('ext', '[1, ["", "ac"]]'))
msgpack_eq(0, sp('ext', '[2, ["", "ac"]]'), sp('ext', '[1, ["", "ac"]]'))
msgpack_eq(0, sp('ext', '[1, ["", "ac"]]'), sp('ext', '[1, ["", "abc"]]'))
end)
it('compares raw maps correctly', function()
msgpack_eq(1, '{"a": 1, "b": 2}', '{"b": 2, "a": 1}')
msgpack_eq(1, '{}', '{}')
msgpack_eq(0, '{}', '{"a": 1}')
msgpack_eq(0, '{"a": 2}', '{"a": 1}')
msgpack_eq(0, '{"a": 1}', '{"b": 1}')
msgpack_eq(0, '{"a": 1}', '{"a": 1, "b": 1}')
msgpack_eq(0, '{"a": 1, "b": 1}', '{"b": 1}')
end)
it('compares map specials correctly', function()
msgpack_eq(1, mapsp(), mapsp())
msgpack_eq(1, mapsp(sp('binary', '[""]'), '""'),
mapsp(sp('binary', '[""]'), '""'))
msgpack_eq(1, mapsp(mapsp('1', '1'), mapsp('1', '1')),
mapsp(mapsp('1', '1'), mapsp('1', '1')))
msgpack_eq(0, mapsp(), mapsp('1', '1'))
msgpack_eq(0, mapsp(sp('binary', '["a"]'), '""'),
mapsp(sp('binary', '[""]'), '""'))
msgpack_eq(0, mapsp(sp('binary', '[""]'), '"a"'),
mapsp(sp('binary', '[""]'), '""'))
msgpack_eq(0, mapsp(sp('binary', '["a"]'), '"a"'),
mapsp(sp('binary', '[""]'), '""'))
msgpack_eq(0, mapsp(mapsp('1', '1'), mapsp('1', '1')),
mapsp(sp('binary', '[""]'), mapsp('1', '1')))
msgpack_eq(0, mapsp(mapsp('1', '1'), mapsp('1', '1')),
mapsp(mapsp('2', '1'), mapsp('1', '1')))
msgpack_eq(0, mapsp(mapsp('1', '1'), mapsp('1', '1')),
mapsp(mapsp('1', '2'), mapsp('1', '1')))
msgpack_eq(0, mapsp(mapsp('1', '1'), mapsp('1', '1')),
mapsp(mapsp('1', '1'), mapsp('2', '1')))
msgpack_eq(0, mapsp(mapsp('1', '1'), mapsp('1', '1')),
mapsp(mapsp('1', '1'), mapsp('1', '2')))
msgpack_eq(1, mapsp(mapsp('2', '1'), mapsp('1', '1'),
mapsp('1', '1'), mapsp('1', '1')),
mapsp(mapsp('1', '1'), mapsp('1', '1'),
mapsp('2', '1'), mapsp('1', '1')))
end)
it('compares map specials with raw maps correctly', function()
msgpack_eq(1, mapsp(), '{}')
msgpack_eq(1, mapsp(sp('string', '["1"]'), '1'), '{"1": 1}')
msgpack_eq(1, mapsp(sp('string', '["1"]'), sp('integer', '[1, 0, 0, 1]')),
'{"1": 1}')
msgpack_eq(0, mapsp(sp('integer', '[1, 0, 0, 1]'), sp('string', '["1"]')),
'{1: "1"}')
msgpack_eq(0, mapsp('"1"', sp('integer', '[1, 0, 0, 1]')),
'{"1": 1}')
msgpack_eq(0,
mapsp(sp('string', '["1"]'), '1', sp('string', '["2"]'), '2'),
'{"1": 1}')
msgpack_eq(0, mapsp(sp('string', '["1"]'), '1'), '{"1": 1, "2": 2}')
end)
it('compares raw arrays correctly', function()
msgpack_eq(1, '[]', '[]')
msgpack_eq(0, '[]', '[1]')
msgpack_eq(1, '[1]', '[1]')
msgpack_eq(1, '[[[1]]]', '[[[1]]]')
msgpack_eq(0, '[[[2]]]', '[[[1]]]')
end)
it('compares array specials correctly', function()
msgpack_eq(1, sp('array', '[]'), sp('array', '[]'))
msgpack_eq(0, sp('array', '[]'), sp('array', '[1]'))
msgpack_eq(1, sp('array', '[1]'), sp('array', '[1]'))
msgpack_eq(1, sp('array', '[[[1]]]'), sp('array', '[[[1]]]'))
msgpack_eq(0, sp('array', '[[[1]]]'), sp('array', '[[[2]]]'))
end)
it('compares array specials with raw arrays correctly', function()
msgpack_eq(1, sp('array', '[]'), '[]')
msgpack_eq(0, sp('array', '[]'), '[1]')
msgpack_eq(1, sp('array', '[1]'), '[1]')
msgpack_eq(1, sp('array', '[[[1]]]'), '[[[1]]]')
msgpack_eq(0, sp('array', '[[[1]]]'), '[[[2]]]')
end)
it('compares raw floats correctly', function()
msgpack_eq(1, '0.0', '0.0')
msgpack_eq(1, '(1.0/0.0-1.0/0.0)', '(1.0/0.0-1.0/0.0)')
if has_minus_nan then
msgpack_eq(0, '(1.0/0.0-1.0/0.0)', '-(1.0/0.0-1.0/0.0)')
end
msgpack_eq(1, '-(1.0/0.0-1.0/0.0)', '-(1.0/0.0-1.0/0.0)')
msgpack_eq(1, '1.0/0.0', '1.0/0.0')
msgpack_eq(1, '-(1.0/0.0)', '-(1.0/0.0)')
msgpack_eq(1, '0.0', '0.0')
msgpack_eq(0, '0.0', '1.0')
msgpack_eq(0, '0.0', '(1.0/0.0-1.0/0.0)')
msgpack_eq(0, '0.0', '1.0/0.0')
msgpack_eq(0, '0.0', '-(1.0/0.0)')
msgpack_eq(0, '1.0/0.0', '-(1.0/0.0)')
msgpack_eq(0, '(1.0/0.0-1.0/0.0)', '-(1.0/0.0)')
msgpack_eq(0, '(1.0/0.0-1.0/0.0)', '1.0/0.0')
end)
it('compares float specials with raw floats correctly', function()
msgpack_eq(1, sp('float', '0.0'), '0.0')
msgpack_eq(1, sp('float', '(1.0/0.0-1.0/0.0)'), '(1.0/0.0-1.0/0.0)')
if has_minus_nan then
msgpack_eq(0, sp('float', '(1.0/0.0-1.0/0.0)'), '-(1.0/0.0-1.0/0.0)')
msgpack_eq(0, sp('float', '-(1.0/0.0-1.0/0.0)'), '(1.0/0.0-1.0/0.0)')
end
msgpack_eq(1, sp('float', '-(1.0/0.0-1.0/0.0)'), '-(1.0/0.0-1.0/0.0)')
msgpack_eq(1, sp('float', '1.0/0.0'), '1.0/0.0')
msgpack_eq(1, sp('float', '-(1.0/0.0)'), '-(1.0/0.0)')
msgpack_eq(1, sp('float', '0.0'), '0.0')
msgpack_eq(0, sp('float', '0.0'), '1.0')
msgpack_eq(0, sp('float', '0.0'), '(1.0/0.0-1.0/0.0)')
msgpack_eq(0, sp('float', '0.0'), '1.0/0.0')
msgpack_eq(0, sp('float', '0.0'), '-(1.0/0.0)')
msgpack_eq(0, sp('float', '1.0/0.0'), '-(1.0/0.0)')
msgpack_eq(0, sp('float', '(1.0/0.0-1.0/0.0)'), '-(1.0/0.0)')
msgpack_eq(0, sp('float', '(1.0/0.0-1.0/0.0)'), '1.0/0.0')
end)
it('compares float specials correctly', function()
msgpack_eq(1, sp('float', '0.0'), sp('float', '0.0'))
msgpack_eq(1, sp('float', '(1.0/0.0-1.0/0.0)'),
sp('float', '(1.0/0.0-1.0/0.0)'))
msgpack_eq(1, sp('float', '1.0/0.0'), sp('float', '1.0/0.0'))
msgpack_eq(1, sp('float', '-(1.0/0.0)'), sp('float', '-(1.0/0.0)'))
msgpack_eq(1, sp('float', '0.0'), sp('float', '0.0'))
msgpack_eq(0, sp('float', '0.0'), sp('float', '1.0'))
msgpack_eq(0, sp('float', '0.0'), sp('float', '(1.0/0.0-1.0/0.0)'))
msgpack_eq(0, sp('float', '0.0'), sp('float', '1.0/0.0'))
msgpack_eq(0, sp('float', '0.0'), sp('float', '-(1.0/0.0)'))
msgpack_eq(0, sp('float', '1.0/0.0'), sp('float', '-(1.0/0.0)'))
msgpack_eq(0, sp('float', '(1.0/0.0-1.0/0.0)'), sp('float', '-(1.0/0.0)'))
if has_minus_nan then
msgpack_eq(0, sp('float', '(1.0/0.0-1.0/0.0)'),
sp('float', '-(1.0/0.0-1.0/0.0)'))
end
msgpack_eq(1, sp('float', '-(1.0/0.0-1.0/0.0)'),
sp('float', '-(1.0/0.0-1.0/0.0)'))
msgpack_eq(0, sp('float', '(1.0/0.0-1.0/0.0)'), sp('float', '1.0/0.0'))
end)
it('compares boolean specials correctly', function()
msgpack_eq(1, sp('boolean', '1'), sp('boolean', '1'))
msgpack_eq(0, sp('boolean', '1'), sp('boolean', '0'))
end)
it('compares nil specials correctly', function()
msgpack_eq(1, sp('nil', '1'), sp('nil', '0'))
end)
it('compares nil, boolean and integer values with each other correctly',
function()
msgpack_eq(0, sp('boolean', '1'), '1')
msgpack_eq(0, sp('boolean', '1'), sp('nil', '0'))
msgpack_eq(0, sp('boolean', '1'), sp('nil', '1'))
msgpack_eq(0, sp('boolean', '0'), sp('nil', '0'))
msgpack_eq(0, sp('boolean', '0'), '0')
msgpack_eq(0, sp('boolean', '0'), sp('integer', '[1, 0, 0, 0]'))
msgpack_eq(0, sp('boolean', '0'), sp('integer', '[1, 0, 0, 1]'))
msgpack_eq(0, sp('boolean', '1'), sp('integer', '[1, 0, 0, 1]'))
msgpack_eq(0, sp('nil', '0'), sp('integer', '[1, 0, 0, 0]'))
msgpack_eq(0, sp('nil', '0'), '0')
end)
end)
describe('function msgpack#is_int', function()
it('works', function()
eq(1, nvim_eval('msgpack#is_int(1)'))
eq(1, nvim_eval('msgpack#is_int(-1)'))
eq(1, nvim_eval(('msgpack#is_int(%s)'):format(
sp('integer', '[1, 0, 0, 1]'))))
eq(1, nvim_eval(('msgpack#is_int(%s)'):format(
sp('integer', '[-1, 0, 0, 1]'))))
eq(0, nvim_eval(('msgpack#is_int(%s)'):format(
sp('float', '0.0'))))
eq(0, nvim_eval(('msgpack#is_int(%s)'):format(
sp('boolean', '0'))))
eq(0, nvim_eval(('msgpack#is_int(%s)'):format(
sp('nil', '0'))))
eq(0, nvim_eval('msgpack#is_int("")'))
end)
end)
describe('function msgpack#is_uint', function()
it('works', function()
eq(1, nvim_eval('msgpack#is_uint(1)'))
eq(0, nvim_eval('msgpack#is_uint(-1)'))
eq(1, nvim_eval(('msgpack#is_uint(%s)'):format(
sp('integer', '[1, 0, 0, 1]'))))
eq(0, nvim_eval(('msgpack#is_uint(%s)'):format(
sp('integer', '[-1, 0, 0, 1]'))))
eq(0, nvim_eval(('msgpack#is_uint(%s)'):format(
sp('float', '0.0'))))
eq(0, nvim_eval(('msgpack#is_uint(%s)'):format(
sp('boolean', '0'))))
eq(0, nvim_eval(('msgpack#is_uint(%s)'):format(
sp('nil', '0'))))
eq(0, nvim_eval('msgpack#is_uint("")'))
end)
end)
describe('function msgpack#strftime', function()
it('works', function()
local epoch = os.date('%Y-%m-%dT%H:%M:%S', 0)
eq(epoch, nvim_eval('msgpack#strftime("%Y-%m-%dT%H:%M:%S", 0)'))
eq(epoch, nvim_eval(
('msgpack#strftime("%%Y-%%m-%%dT%%H:%%M:%%S", %s)'):format(sp(
'integer', '[1, 0, 0, 0]'))))
end)
end)
describe('function msgpack#strptime', function()
it('works', function()
for _, v in ipairs({0, 10, 100000, 204, 1000000000}) do
local time = os.date('%Y-%m-%dT%H:%M:%S', v)
eq(v, nvim_eval('msgpack#strptime("%Y-%m-%dT%H:%M:%S", '
.. '"' .. time .. '")'))
end
end)
end)
describe('function msgpack#type', function()
local type_eq = function(expected, val)
eq(expected, nvim_eval(('msgpack#type(%s)'):format(val)))
end
it('works for special dictionaries', function()
type_eq('string', sp('string', '[""]'))
type_eq('binary', sp('binary', '[""]'))
type_eq('ext', sp('ext', '[1, [""]]'))
type_eq('array', sp('array', '[]'))
type_eq('map', sp('map', '[]'))
type_eq('integer', sp('integer', '[1, 0, 0, 0]'))
type_eq('float', sp('float', '0.0'))
type_eq('boolean', sp('boolean', '0'))
type_eq('nil', sp('nil', '0'))
end)
it('works for regular values', function()
type_eq('binary', '""')
type_eq('array', '[]')
type_eq('map', '{}')
type_eq('integer', '1')
type_eq('float', '0.0')
type_eq('float', '(1.0/0.0)')
type_eq('float', '-(1.0/0.0)')
type_eq('float', '(1.0/0.0-1.0/0.0)')
end)
end)
describe('function msgpack#special_type', function()
local sp_type_eq = function(expected, val)
eq(expected, nvim_eval(('msgpack#special_type(%s)'):format(val)))
end
it('works for special dictionaries', function()
sp_type_eq('string', sp('string', '[""]'))
sp_type_eq('binary', sp('binary', '[""]'))
sp_type_eq('ext', sp('ext', '[1, [""]]'))
sp_type_eq('array', sp('array', '[]'))
sp_type_eq('map', sp('map', '[]'))
sp_type_eq('integer', sp('integer', '[1, 0, 0, 0]'))
sp_type_eq('float', sp('float', '0.0'))
sp_type_eq('boolean', sp('boolean', '0'))
sp_type_eq('nil', sp('nil', '0'))
end)
it('works for regular values', function()
sp_type_eq(0, '""')
sp_type_eq(0, '[]')
sp_type_eq(0, '{}')
sp_type_eq(0, '1')
sp_type_eq(0, '0.0')
sp_type_eq(0, '(1.0/0.0)')
sp_type_eq(0, '-(1.0/0.0)')
sp_type_eq(0, '(1.0/0.0-1.0/0.0)')
end)
end)
describe('function msgpack#string', function()
local string_eq = function(expected, val)
eq(expected, nvim_eval(('msgpack#string(%s)'):format(val)))
end
it('works for special dictionaries', function()
string_eq('=""', sp('string', '[""]'))
string_eq('="\\n"', sp('string', '["", ""]'))
string_eq('="ab\\0c\\nde"', sp('string', '["ab\\nc", "de"]'))
string_eq('""', sp('binary', '[""]'))
string_eq('"\\n"', sp('binary', '["", ""]'))
string_eq('"ab\\0c\\nde"', sp('binary', '["ab\\nc", "de"]'))
string_eq('+(2)""', sp('ext', '[2, [""]]'))
string_eq('+(2)"\\n"', sp('ext', '[2, ["", ""]]'))
string_eq('+(2)"ab\\0c\\nde"', sp('ext', '[2, ["ab\\nc", "de"]]'))
string_eq('[]', sp('array', '[]'))
string_eq('[[[[{}]]]]', sp('array', '[[[[{}]]]]'))
string_eq('{}', sp('map', '[]'))
string_eq('{2: 10}', sp('map', '[[2, 10]]'))
string_eq('{{1: 1}: {1: 1}, {2: 1}: {1: 1}}',
mapsp(mapsp('2', '1'), mapsp('1', '1'),
mapsp('1', '1'), mapsp('1', '1')))
string_eq('{{1: 1}: {1: 1}, {2: 1}: {1: 1}}',
mapsp(mapsp('1', '1'), mapsp('1', '1'),
mapsp('2', '1'), mapsp('1', '1')))
string_eq('{[1, 2, {{1: 2}: 1}]: [1, 2, {{1: 2}: 1}]}',
mapsp(('[1, 2, %s]'):format(mapsp(mapsp('1', '2'), '1')),
('[1, 2, %s]'):format(mapsp(mapsp('1', '2'), '1'))))
string_eq('0x0000000000000000', sp('integer', '[1, 0, 0, 0]'))
string_eq('-0x0000000100000000', sp('integer', '[-1, 0, 2, 0]'))
string_eq('0x123456789abcdef0',
sp('integer', '[ 1, 0, 610839793, 448585456]'))
string_eq('-0x123456789abcdef0',
sp('integer', '[-1, 0, 610839793, 448585456]'))
string_eq('0xf23456789abcdef0',
sp('integer', '[ 1, 3, 1684581617, 448585456]'))
string_eq('-0x723456789abcdef0',
sp('integer', '[-1, 1, 1684581617, 448585456]'))
string_eq('0.0', sp('float', '0.0'))
string_eq('inf', sp('float', '(1.0/0.0)'))
string_eq('-inf', sp('float', '-(1.0/0.0)'))
if has_minus_nan then
string_eq('-nan', sp('float', '(1.0/0.0-1.0/0.0)'))
end
string_eq('nan', sp('float', '-(1.0/0.0-1.0/0.0)'))
string_eq('FALSE', sp('boolean', '0'))
string_eq('TRUE', sp('boolean', '1'))
string_eq('NIL', sp('nil', '0'))
end)
it('works for regular values', function()
string_eq('""', '""')
string_eq('"\\n"', '"\\n"')
string_eq('[]', '[]')
string_eq('[[[{}]]]', '[[[{}]]]')
string_eq('{}', '{}')
string_eq('{="2": 10}', '{2: 10}')
string_eq('{="2": [{}]}', '{2: [{}]}')
string_eq('1', '1')
string_eq('0.0', '0.0')
string_eq('inf', '(1.0/0.0)')
string_eq('-inf', '-(1.0/0.0)')
if has_minus_nan then
string_eq('-nan', '(1.0/0.0-1.0/0.0)')
end
string_eq('nan', '-(1.0/0.0-1.0/0.0)')
end)
end)
describe('function msgpack#deepcopy', function()
it('works for special dictionaries', function()
nvim_command('let sparr = ' .. sp('array', '[[[]]]'))
nvim_command('let spmap = ' .. mapsp('"abc"', '[[]]'))
nvim_command('let spint = ' .. sp('integer', '[1, 0, 0, 0]'))
nvim_command('let spflt = ' .. sp('float', '1.0'))
nvim_command('let spext = ' .. sp('ext', '[2, ["abc", "def"]]'))
nvim_command('let spstr = ' .. sp('string', '["abc", "def"]'))
nvim_command('let spbin = ' .. sp('binary', '["abc", "def"]'))
nvim_command('let spbln = ' .. sp('boolean', '0'))
nvim_command('let spnil = ' .. sp('nil', '0'))
nvim_command('let sparr2 = msgpack#deepcopy(sparr)')
nvim_command('let spmap2 = msgpack#deepcopy(spmap)')
nvim_command('let spint2 = msgpack#deepcopy(spint)')
nvim_command('let spflt2 = msgpack#deepcopy(spflt)')
nvim_command('let spext2 = msgpack#deepcopy(spext)')
nvim_command('let spstr2 = msgpack#deepcopy(spstr)')
nvim_command('let spbin2 = msgpack#deepcopy(spbin)')
nvim_command('let spbln2 = msgpack#deepcopy(spbln)')
nvim_command('let spnil2 = msgpack#deepcopy(spnil)')
eq('array', nvim_eval('msgpack#type(sparr2)'))
eq('map', nvim_eval('msgpack#type(spmap2)'))
eq('integer', nvim_eval('msgpack#type(spint2)'))
eq('float', nvim_eval('msgpack#type(spflt2)'))
eq('ext', nvim_eval('msgpack#type(spext2)'))
eq('string', nvim_eval('msgpack#type(spstr2)'))
eq('binary', nvim_eval('msgpack#type(spbin2)'))
eq('boolean', nvim_eval('msgpack#type(spbln2)'))
eq('nil', nvim_eval('msgpack#type(spnil2)'))
nvim_command('call add(sparr._VAL, 0)')
nvim_command('call add(sparr._VAL[0], 0)')
nvim_command('call add(sparr._VAL[0][0], 0)')
nvim_command('call add(spmap._VAL, [0, 0])')
nvim_command('call add(spmap._VAL[0][1], 0)')
nvim_command('call add(spmap._VAL[0][1][0], 0)')
nvim_command('let spint._VAL[1] = 1')
nvim_command('let spflt._VAL = 0.0')
nvim_command('let spext._VAL[0] = 3')
nvim_command('let spext._VAL[1][0] = "gh"')
nvim_command('let spstr._VAL[0] = "gh"')
nvim_command('let spbin._VAL[0] = "gh"')
nvim_command('let spbln._VAL = 1')
nvim_command('let spnil._VAL = 1')
eq({_TYPE={}, _VAL={{{}}}}, nvim_eval('sparr2'))
eq({_TYPE={}, _VAL={{'abc', {{}}}}}, nvim_eval('spmap2'))
eq({_TYPE={}, _VAL={1, 0, 0, 0}}, nvim_eval('spint2'))
eq({_TYPE={}, _VAL=1.0}, nvim_eval('spflt2'))
eq({_TYPE={}, _VAL={2, {'abc', 'def'}}}, nvim_eval('spext2'))
eq({_TYPE={}, _VAL={'abc', 'def'}}, nvim_eval('spstr2'))
eq({_TYPE={}, _VAL={'abc', 'def'}}, nvim_eval('spbin2'))
eq({_TYPE={}, _VAL=0}, nvim_eval('spbln2'))
eq({_TYPE={}, _VAL=0}, nvim_eval('spnil2'))
nvim_command('let sparr._TYPE = []')
nvim_command('let spmap._TYPE = []')
nvim_command('let spint._TYPE = []')
nvim_command('let spflt._TYPE = []')
nvim_command('let spext._TYPE = []')
nvim_command('let spstr._TYPE = []')
nvim_command('let spbin._TYPE = []')
nvim_command('let spbln._TYPE = []')
nvim_command('let spnil._TYPE = []')
eq('array', nvim_eval('msgpack#special_type(sparr2)'))
eq('map', nvim_eval('msgpack#special_type(spmap2)'))
eq('integer', nvim_eval('msgpack#special_type(spint2)'))
eq('float', nvim_eval('msgpack#special_type(spflt2)'))
eq('ext', nvim_eval('msgpack#special_type(spext2)'))
eq('string', nvim_eval('msgpack#special_type(spstr2)'))
eq('binary', nvim_eval('msgpack#special_type(spbin2)'))
eq('boolean', nvim_eval('msgpack#special_type(spbln2)'))
eq('nil', nvim_eval('msgpack#special_type(spnil2)'))
end)
it('works for regular values', function()
nvim_command('let arr = [[[]]]')
nvim_command('let map = {1: {}}')
nvim_command('let int = 1')
nvim_command('let flt = 2.0')
nvim_command('let bin = "abc"')
nvim_command('let arr2 = msgpack#deepcopy(arr)')
nvim_command('let map2 = msgpack#deepcopy(map)')
nvim_command('let int2 = msgpack#deepcopy(int)')
nvim_command('let flt2 = msgpack#deepcopy(flt)')
nvim_command('let bin2 = msgpack#deepcopy(bin)')
eq('array', nvim_eval('msgpack#type(arr2)'))
eq('map', nvim_eval('msgpack#type(map2)'))
eq('integer', nvim_eval('msgpack#type(int2)'))
eq('float', nvim_eval('msgpack#type(flt2)'))
eq('binary', nvim_eval('msgpack#type(bin2)'))
nvim_command('call add(arr, 0)')
nvim_command('call add(arr[0], 0)')
nvim_command('call add(arr[0][0], 0)')
nvim_command('let map.a = 1')
nvim_command('let map.1.a = 1')
nvim_command('let int = 2')
nvim_command('let flt = 3.0')
nvim_command('let bin = ""')
eq({{{}}}, nvim_eval('arr2'))
eq({['1']={}}, nvim_eval('map2'))
eq(1, nvim_eval('int2'))
eq(2.0, nvim_eval('flt2'))
eq('abc', nvim_eval('bin2'))
end)
end)
describe('function msgpack#eval', function()
local eval_eq = function(expected_type, expected_val, str, ...)
nvim_command(('let g:__val = msgpack#eval(\'%s\', %s)'):format(str:gsub(
'\'', '\'\''), select(1, ...) or '{}'))
eq(expected_type, nvim_eval('msgpack#type(g:__val)'))
local expected_val_full = expected_val
if (not (({float=true, integer=true})[expected_type]
and type(expected_val) ~= 'table')
and expected_type ~= 'array') then
expected_val_full = {_TYPE={}, _VAL=expected_val_full}
end
if expected_val_full == expected_val_full then
eq(expected_val_full, nvim_eval('g:__val'))
else
eq(tostring(expected_val_full), tostring(nvim_eval('g:__val')))
end
nvim_command('unlet g:__val')
end
it('correctly loads binary strings', function()
eval_eq('binary', {'abcdef'}, '"abcdef"')
eval_eq('binary', {'abc', 'def'}, '"abc\\ndef"')
eval_eq('binary', {'abc\ndef'}, '"abc\\0def"')
eval_eq('binary', {'\nabc\ndef\n'}, '"\\0abc\\0def\\0"')
eval_eq('binary', {'abc\n\n\ndef'}, '"abc\\0\\0\\0def"')
eval_eq('binary', {'abc\n', '\ndef'}, '"abc\\0\\n\\0def"')
eval_eq('binary', {'abc', '', '', 'def'}, '"abc\\n\\n\\ndef"')
eval_eq('binary', {'abc', '', '', 'def', ''}, '"abc\\n\\n\\ndef\\n"')
eval_eq('binary', {'', 'abc', '', '', 'def'}, '"\\nabc\\n\\n\\ndef"')
eval_eq('binary', {''}, '""')
eval_eq('binary', {'"'}, '"\\""')
end)
it('correctly loads strings', function()
eval_eq('string', {'abcdef'}, '="abcdef"')
eval_eq('string', {'abc', 'def'}, '="abc\\ndef"')
eval_eq('string', {'abc\ndef'}, '="abc\\0def"')
eval_eq('string', {'\nabc\ndef\n'}, '="\\0abc\\0def\\0"')
eval_eq('string', {'abc\n\n\ndef'}, '="abc\\0\\0\\0def"')
eval_eq('string', {'abc\n', '\ndef'}, '="abc\\0\\n\\0def"')
eval_eq('string', {'abc', '', '', 'def'}, '="abc\\n\\n\\ndef"')
eval_eq('string', {'abc', '', '', 'def', ''}, '="abc\\n\\n\\ndef\\n"')
eval_eq('string', {'', 'abc', '', '', 'def'}, '="\\nabc\\n\\n\\ndef"')
eval_eq('string', {''}, '=""')
eval_eq('string', {'"'}, '="\\""')
end)
it('correctly loads ext values', function()
eval_eq('ext', {0, {'abcdef'}}, '+(0)"abcdef"')
eval_eq('ext', {0, {'abc', 'def'}}, '+(0)"abc\\ndef"')
eval_eq('ext', {0, {'abc\ndef'}}, '+(0)"abc\\0def"')
eval_eq('ext', {0, {'\nabc\ndef\n'}}, '+(0)"\\0abc\\0def\\0"')
eval_eq('ext', {0, {'abc\n\n\ndef'}}, '+(0)"abc\\0\\0\\0def"')
eval_eq('ext', {0, {'abc\n', '\ndef'}}, '+(0)"abc\\0\\n\\0def"')
eval_eq('ext', {0, {'abc', '', '', 'def'}}, '+(0)"abc\\n\\n\\ndef"')
eval_eq('ext', {0, {'abc', '', '', 'def', ''}},
'+(0)"abc\\n\\n\\ndef\\n"')
eval_eq('ext', {0, {'', 'abc', '', '', 'def'}},
'+(0)"\\nabc\\n\\n\\ndef"')
eval_eq('ext', {0, {''}}, '+(0)""')
eval_eq('ext', {0, {'"'}}, '+(0)"\\""')
eval_eq('ext', {-1, {'abcdef'}}, '+(-1)"abcdef"')
eval_eq('ext', {-1, {'abc', 'def'}}, '+(-1)"abc\\ndef"')
eval_eq('ext', {-1, {'abc\ndef'}}, '+(-1)"abc\\0def"')
eval_eq('ext', {-1, {'\nabc\ndef\n'}}, '+(-1)"\\0abc\\0def\\0"')
eval_eq('ext', {-1, {'abc\n\n\ndef'}}, '+(-1)"abc\\0\\0\\0def"')
eval_eq('ext', {-1, {'abc\n', '\ndef'}}, '+(-1)"abc\\0\\n\\0def"')
eval_eq('ext', {-1, {'abc', '', '', 'def'}}, '+(-1)"abc\\n\\n\\ndef"')
eval_eq('ext', {-1, {'abc', '', '', 'def', ''}},
'+(-1)"abc\\n\\n\\ndef\\n"')
eval_eq('ext', {-1, {'', 'abc', '', '', 'def'}},
'+(-1)"\\nabc\\n\\n\\ndef"')
eval_eq('ext', {-1, {''}}, '+(-1)""')
eval_eq('ext', {-1, {'"'}}, '+(-1)"\\""')
end)
it('correctly loads floats', function()
eval_eq('float', inf, 'inf')
eval_eq('float', minus_inf, '-inf')
eval_eq('float', nan, 'nan')
eval_eq('float', minus_nan, '-nan')
eval_eq('float', 1.0e10, '1.0e10')
eval_eq('float', 1.0e10, '1.0e+10')
eval_eq('float', -1.0e10, '-1.0e+10')
eval_eq('float', 1.0, '1.0')
eval_eq('float', -1.0, '-1.0')
eval_eq('float', 1.0e-10, '1.0e-10')
eval_eq('float', -1.0e-10, '-1.0e-10')
end)
it('correctly loads integers', function()
eval_eq('integer', 10, '10')
eval_eq('integer', -10, '-10')
eval_eq('integer', { 1, 0, 610839793, 448585456}, ' 0x123456789ABCDEF0')
eval_eq('integer', {-1, 0, 610839793, 448585456}, '-0x123456789ABCDEF0')
eval_eq('integer', { 1, 3, 1684581617, 448585456}, ' 0xF23456789ABCDEF0')
eval_eq('integer', {-1, 1, 1684581617, 448585456}, '-0x723456789ABCDEF0')
eval_eq('integer', { 1, 0, 0, 0x100}, '0x100')
eval_eq('integer', {-1, 0, 0, 0x100}, '-0x100')
eval_eq('integer', ('a'):byte(), '\'a\'')
eval_eq('integer', 0xAB, '\'«\'')
end)
it('correctly loads constants', function()
eval_eq('boolean', 1, 'TRUE')
eval_eq('boolean', 0, 'FALSE')
eval_eq('nil', 0, 'NIL')
eval_eq('nil', 0, 'NIL', '{"NIL": 1, "nan": 2, "T": 3}')
eval_eq('float', nan, 'nan',
'{"NIL": "1", "nan": "2", "T": "3"}')
eval_eq('integer', 3, 'T', '{"NIL": "1", "nan": "2", "T": "3"}')
eval_eq('integer', {1, 0, 0, 0}, 'T',
('{"NIL": "1", "nan": "2", "T": \'%s\'}'):format(
sp('integer', '[1, 0, 0, 0]')))
end)
it('correctly loads maps', function()
eval_eq('map', {}, '{}')
eval_eq('map', {{{_TYPE={}, _VAL={{1, 2}}}, {_TYPE={}, _VAL={{3, 4}}}}},
'{{1: 2}: {3: 4}}')
eval_eq('map', {{{_TYPE={}, _VAL={{1, 2}}}, {_TYPE={}, _VAL={{3, 4}}}},
{1, 2}},
'{{1: 2}: {3: 4}, 1: 2}')
end)
it('correctly loads arrays', function()
eval_eq('array', {}, '[]')
eval_eq('array', {1}, '[1]')
eval_eq('array', {{_TYPE={}, _VAL=1}}, '[TRUE]')
eval_eq('array', {{{_TYPE={}, _VAL={{1, 2}}}}, {_TYPE={}, _VAL={{3, 4}}}},
'[[{1: 2}], {3: 4}]')
end)
it('errors out when needed', function()
eq('empty:Parsed string is empty',
exc_exec('call msgpack#eval("", {})'))
eq('unknown:Invalid non-space character: ^',
exc_exec('call msgpack#eval("^", {})'))
eq('char-invalid:Invalid integer character literal format: \'\'',
exc_exec('call msgpack#eval("\'\'", {})'))
eq('char-invalid:Invalid integer character literal format: \'ab\'',
exc_exec('call msgpack#eval("\'ab\'", {})'))
eq('char-invalid:Invalid integer character literal format: \'',
exc_exec('call msgpack#eval("\'", {})'))
eq('"-invalid:Invalid string: "',
exc_exec('call msgpack#eval("\\"", {})'))
eq('"-invalid:Invalid string: ="',
exc_exec('call msgpack#eval("=\\"", {})'))
eq('"-invalid:Invalid string: +(0)"',
exc_exec('call msgpack#eval("+(0)\\"", {})'))
eq('0.-nodigits:Decimal dot must be followed by digit(s): .e1',
exc_exec('call msgpack#eval("0.e1", {})'))
eq('0x-long:Must have at most 16 hex digits: FEDCBA98765432100',
exc_exec('call msgpack#eval("0xFEDCBA98765432100", {})'))
eq('0x-empty:Must have number after 0x: ',
exc_exec('call msgpack#eval("0x", {})'))
eq('name-unknown:Unknown name FOO: FOO',
exc_exec('call msgpack#eval("FOO", {})'))
end)
end)
end)

File diff suppressed because it is too large Load Diff