Merge branch 'master' into s-dash-stdin

This commit is contained in:
b-r-o-c-k
2018-04-14 14:17:51 -05:00
473 changed files with 45843 additions and 11569 deletions

View File

@@ -0,0 +1,320 @@
local helpers = require("test.unit.helpers")(after_each)
local bit = require('bit')
local itp = helpers.gen_itp(it)
local child_call_once = helpers.child_call_once
local cimport = helpers.cimport
local ffi = helpers.ffi
local lib = cimport('./src/nvim/charset.h')
local ARGTYPES
child_call_once(function()
ARGTYPES = {
num = ffi.typeof('varnumber_T[1]'),
unum = ffi.typeof('uvarnumber_T[1]'),
pre = ffi.typeof('int[1]'),
len = ffi.typeof('int[1]'),
}
end)
local icnt = -42
local ucnt = 4242
local function arginit(arg)
if arg == 'unum' then
ucnt = ucnt + 1
return ARGTYPES[arg]({ucnt})
else
icnt = icnt - 1
return ARGTYPES[arg]({icnt})
end
end
local function argreset(arg, args)
if arg == 'unum' then
ucnt = ucnt + 1
args[arg][0] = ucnt
else
icnt = icnt - 1
args[arg][0] = icnt
end
end
local function test_vim_str2nr(s, what, exp, maxlen)
local bits = {}
for k, _ in pairs(exp) do
bits[#bits + 1] = k
end
maxlen = maxlen or #s
local args = {}
for k, _ in pairs(ARGTYPES) do
args[k] = arginit(k)
end
for case = 0, ((2 ^ (#bits)) - 1) do
local cv = {}
for b = 0, (#bits - 1) do
if bit.band(case, (2 ^ b)) == 0 then
local k = bits[b + 1]
argreset(k, args)
cv[k] = args[k]
end
end
lib.vim_str2nr(s, cv.pre, cv.len, what, cv.num, cv.unum, maxlen)
for cck, ccv in pairs(cv) do
if exp[cck] ~= tonumber(ccv[0]) then
error(('Failed check (%s = %d) in test (s=%s, w=%u, m=%d): %d'):format(
cck, exp[cck], s, tonumber(what), maxlen, tonumber(ccv[0])
))
end
end
end
end
local _itp = itp
itp = function(...)
collectgarbage('restart')
_itp(...)
end
describe('vim_str2nr()', function()
itp('works fine when it has nothing to do', function()
test_vim_str2nr('', 0, {len = 0, num = 0, unum = 0, pre = 0}, 0)
test_vim_str2nr('', lib.STR2NR_ALL, {len = 0, num = 0, unum = 0, pre = 0}, 0)
test_vim_str2nr('', lib.STR2NR_BIN, {len = 0, num = 0, unum = 0, pre = 0}, 0)
test_vim_str2nr('', lib.STR2NR_OCT, {len = 0, num = 0, unum = 0, pre = 0}, 0)
test_vim_str2nr('', lib.STR2NR_HEX, {len = 0, num = 0, unum = 0, pre = 0}, 0)
test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_DEC, {len = 0, num = 0, unum = 0, pre = 0}, 0)
test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_BIN, {len = 0, num = 0, unum = 0, pre = 0}, 0)
test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_OCT, {len = 0, num = 0, unum = 0, pre = 0}, 0)
test_vim_str2nr('', lib.STR2NR_FORCE + lib.STR2NR_HEX, {len = 0, num = 0, unum = 0, pre = 0}, 0)
end)
itp('works with decimal numbers', function()
for _, flags in ipairs({
0,
lib.STR2NR_BIN,
lib.STR2NR_OCT,
lib.STR2NR_HEX,
lib.STR2NR_BIN + lib.STR2NR_OCT,
lib.STR2NR_BIN + lib.STR2NR_HEX,
lib.STR2NR_OCT + lib.STR2NR_HEX,
lib.STR2NR_ALL,
lib.STR2NR_FORCE + lib.STR2NR_DEC,
}) do
-- Check that all digits are recognized
test_vim_str2nr( '12345', flags, {len = 5, num = 12345, unum = 12345, pre = 0}, 0)
test_vim_str2nr( '67890', flags, {len = 5, num = 67890, unum = 67890, pre = 0}, 0)
test_vim_str2nr( '12345A', flags, {len = 5, num = 12345, unum = 12345, pre = 0}, 0)
test_vim_str2nr( '67890A', flags, {len = 5, num = 67890, unum = 67890, pre = 0}, 0)
test_vim_str2nr( '42', flags, {len = 2, num = 42, unum = 42, pre = 0}, 0)
test_vim_str2nr( '42', flags, {len = 1, num = 4, unum = 4, pre = 0}, 1)
test_vim_str2nr( '42', flags, {len = 2, num = 42, unum = 42, pre = 0}, 2)
test_vim_str2nr( '42', flags, {len = 2, num = 42, unum = 42, pre = 0}, 3) -- includes NUL byte in maxlen
test_vim_str2nr( '42x', flags, {len = 2, num = 42, unum = 42, pre = 0}, 0)
test_vim_str2nr( '42x', flags, {len = 2, num = 42, unum = 42, pre = 0}, 3)
test_vim_str2nr('-42', flags, {len = 3, num = -42, unum = 42, pre = 0}, 3)
test_vim_str2nr('-42', flags, {len = 1, num = 0, unum = 0, pre = 0}, 1)
test_vim_str2nr('-42x', flags, {len = 3, num = -42, unum = 42, pre = 0}, 0)
test_vim_str2nr('-42x', flags, {len = 3, num = -42, unum = 42, pre = 0}, 4)
end
end)
itp('works with binary numbers', function()
for _, flags in ipairs({
lib.STR2NR_BIN,
lib.STR2NR_BIN + lib.STR2NR_OCT,
lib.STR2NR_BIN + lib.STR2NR_HEX,
lib.STR2NR_ALL,
lib.STR2NR_FORCE + lib.STR2NR_BIN,
}) do
local bin
local BIN
if flags > lib.STR2NR_FORCE then
bin = 0
BIN = 0
else
bin = ('b'):byte()
BIN = ('B'):byte()
end
test_vim_str2nr( '0b101', flags, {len = 5, num = 5, unum = 5, pre = bin}, 0)
test_vim_str2nr( '0b101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
test_vim_str2nr( '0b101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2)
test_vim_str2nr( '0b101', flags, {len = 3, num = 1, unum = 1, pre = bin}, 3)
test_vim_str2nr( '0b101', flags, {len = 4, num = 2, unum = 2, pre = bin}, 4)
test_vim_str2nr( '0b101', flags, {len = 5, num = 5, unum = 5, pre = bin}, 5)
test_vim_str2nr( '0b101', flags, {len = 5, num = 5, unum = 5, pre = bin}, 6)
test_vim_str2nr( '0b1012', flags, {len = 5, num = 5, unum = 5, pre = bin}, 0)
test_vim_str2nr( '0b1012', flags, {len = 5, num = 5, unum = 5, pre = bin}, 6)
test_vim_str2nr('-0b101', flags, {len = 6, num = -5, unum = 5, pre = bin}, 0)
test_vim_str2nr('-0b101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
test_vim_str2nr('-0b101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2)
test_vim_str2nr('-0b101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3)
test_vim_str2nr('-0b101', flags, {len = 4, num = -1, unum = 1, pre = bin}, 4)
test_vim_str2nr('-0b101', flags, {len = 5, num = -2, unum = 2, pre = bin}, 5)
test_vim_str2nr('-0b101', flags, {len = 6, num = -5, unum = 5, pre = bin}, 6)
test_vim_str2nr('-0b101', flags, {len = 6, num = -5, unum = 5, pre = bin}, 7)
test_vim_str2nr('-0b1012', flags, {len = 6, num = -5, unum = 5, pre = bin}, 0)
test_vim_str2nr('-0b1012', flags, {len = 6, num = -5, unum = 5, pre = bin}, 7)
test_vim_str2nr( '0B101', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 0)
test_vim_str2nr( '0B101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
test_vim_str2nr( '0B101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2)
test_vim_str2nr( '0B101', flags, {len = 3, num = 1, unum = 1, pre = BIN}, 3)
test_vim_str2nr( '0B101', flags, {len = 4, num = 2, unum = 2, pre = BIN}, 4)
test_vim_str2nr( '0B101', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 5)
test_vim_str2nr( '0B101', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 6)
test_vim_str2nr( '0B1012', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 0)
test_vim_str2nr( '0B1012', flags, {len = 5, num = 5, unum = 5, pre = BIN}, 6)
test_vim_str2nr('-0B101', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 0)
test_vim_str2nr('-0B101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
test_vim_str2nr('-0B101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2)
test_vim_str2nr('-0B101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3)
test_vim_str2nr('-0B101', flags, {len = 4, num = -1, unum = 1, pre = BIN}, 4)
test_vim_str2nr('-0B101', flags, {len = 5, num = -2, unum = 2, pre = BIN}, 5)
test_vim_str2nr('-0B101', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 6)
test_vim_str2nr('-0B101', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 7)
test_vim_str2nr('-0B1012', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 0)
test_vim_str2nr('-0B1012', flags, {len = 6, num = -5, unum = 5, pre = BIN}, 7)
if flags > lib.STR2NR_FORCE then
test_vim_str2nr('-101', flags, {len = 4, num = -5, unum = 5, pre = 0}, 0)
end
end
end)
itp('works with octal numbers', function()
for _, flags in ipairs({
lib.STR2NR_OCT,
lib.STR2NR_OCT + lib.STR2NR_BIN,
lib.STR2NR_OCT + lib.STR2NR_HEX,
lib.STR2NR_ALL,
lib.STR2NR_FORCE + lib.STR2NR_OCT,
}) do
local oct
if flags > lib.STR2NR_FORCE then
oct = 0
else
oct = ('0'):byte()
end
-- Check that all digits are recognized
test_vim_str2nr( '012345670', flags, {len = 9, num = 2739128, unum = 2739128, pre = oct}, 0)
test_vim_str2nr( '054', flags, {len = 3, num = 44, unum = 44, pre = oct}, 0)
test_vim_str2nr( '054', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
test_vim_str2nr( '054', flags, {len = 2, num = 5, unum = 5, pre = oct}, 2)
test_vim_str2nr( '054', flags, {len = 3, num = 44, unum = 44, pre = oct}, 3)
test_vim_str2nr( '0548', flags, {len = 3, num = 44, unum = 44, pre = oct}, 3)
test_vim_str2nr( '054', flags, {len = 3, num = 44, unum = 44, pre = oct}, 4)
test_vim_str2nr( '054x', flags, {len = 3, num = 44, unum = 44, pre = oct}, 4)
test_vim_str2nr( '054x', flags, {len = 3, num = 44, unum = 44, pre = oct}, 0)
test_vim_str2nr('-054', flags, {len = 4, num = -44, unum = 44, pre = oct}, 0)
test_vim_str2nr('-054', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
test_vim_str2nr('-054', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2)
test_vim_str2nr('-054', flags, {len = 3, num = -5, unum = 5, pre = oct}, 3)
test_vim_str2nr('-054', flags, {len = 4, num = -44, unum = 44, pre = oct}, 4)
test_vim_str2nr('-0548', flags, {len = 4, num = -44, unum = 44, pre = oct}, 4)
test_vim_str2nr('-054', flags, {len = 4, num = -44, unum = 44, pre = oct}, 5)
test_vim_str2nr('-054x', flags, {len = 4, num = -44, unum = 44, pre = oct}, 5)
test_vim_str2nr('-054x', flags, {len = 4, num = -44, unum = 44, pre = oct}, 0)
if flags > lib.STR2NR_FORCE then
test_vim_str2nr('-54', flags, {len = 3, num = -44, unum = 44, pre = 0}, 0)
test_vim_str2nr('-0548', flags, {len = 4, num = -44, unum = 44, pre = 0}, 5)
test_vim_str2nr('-0548', flags, {len = 4, num = -44, unum = 44, pre = 0}, 0)
else
test_vim_str2nr('-0548', flags, {len = 5, num = -548, unum = 548, pre = 0}, 5)
test_vim_str2nr('-0548', flags, {len = 5, num = -548, unum = 548, pre = 0}, 0)
end
end
end)
itp('works with hexadecimal numbers', function()
for _, flags in ipairs({
lib.STR2NR_HEX,
lib.STR2NR_HEX + lib.STR2NR_BIN,
lib.STR2NR_HEX + lib.STR2NR_OCT,
lib.STR2NR_ALL,
lib.STR2NR_FORCE + lib.STR2NR_HEX,
}) do
local hex
local HEX
if flags > lib.STR2NR_FORCE then
hex = 0
HEX = 0
else
hex = ('x'):byte()
HEX = ('X'):byte()
end
-- Check that all digits are recognized
test_vim_str2nr('0x12345', flags, {len = 7, num = 74565, unum = 74565, pre = hex}, 0)
test_vim_str2nr('0x67890', flags, {len = 7, num = 424080, unum = 424080, pre = hex}, 0)
test_vim_str2nr('0xABCDEF', flags, {len = 8, num = 11259375, unum = 11259375, pre = hex}, 0)
test_vim_str2nr('0xabcdef', flags, {len = 8, num = 11259375, unum = 11259375, pre = hex}, 0)
test_vim_str2nr( '0x101', flags, {len = 5, num = 257, unum =257, pre = hex}, 0)
test_vim_str2nr( '0x101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
test_vim_str2nr( '0x101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2)
test_vim_str2nr( '0x101', flags, {len = 3, num = 1, unum = 1, pre = hex}, 3)
test_vim_str2nr( '0x101', flags, {len = 4, num = 16, unum = 16, pre = hex}, 4)
test_vim_str2nr( '0x101', flags, {len = 5, num = 257, unum =257, pre = hex}, 5)
test_vim_str2nr( '0x101', flags, {len = 5, num = 257, unum =257, pre = hex}, 6)
test_vim_str2nr( '0x101G', flags, {len = 5, num = 257, unum =257, pre = hex}, 0)
test_vim_str2nr( '0x101G', flags, {len = 5, num = 257, unum =257, pre = hex}, 6)
test_vim_str2nr('-0x101', flags, {len = 6, num =-257, unum =257, pre = hex}, 0)
test_vim_str2nr('-0x101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
test_vim_str2nr('-0x101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2)
test_vim_str2nr('-0x101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3)
test_vim_str2nr('-0x101', flags, {len = 4, num = -1, unum = 1, pre = hex}, 4)
test_vim_str2nr('-0x101', flags, {len = 5, num = -16, unum = 16, pre = hex}, 5)
test_vim_str2nr('-0x101', flags, {len = 6, num =-257, unum =257, pre = hex}, 6)
test_vim_str2nr('-0x101', flags, {len = 6, num =-257, unum =257, pre = hex}, 7)
test_vim_str2nr('-0x101G', flags, {len = 6, num =-257, unum =257, pre = hex}, 0)
test_vim_str2nr('-0x101G', flags, {len = 6, num =-257, unum =257, pre = hex}, 7)
test_vim_str2nr( '0X101', flags, {len = 5, num = 257, unum =257, pre = HEX}, 0)
test_vim_str2nr( '0X101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
test_vim_str2nr( '0X101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 2)
test_vim_str2nr( '0X101', flags, {len = 3, num = 1, unum = 1, pre = HEX}, 3)
test_vim_str2nr( '0X101', flags, {len = 4, num = 16, unum = 16, pre = HEX}, 4)
test_vim_str2nr( '0X101', flags, {len = 5, num = 257, unum =257, pre = HEX}, 5)
test_vim_str2nr( '0X101', flags, {len = 5, num = 257, unum =257, pre = HEX}, 6)
test_vim_str2nr( '0X101G', flags, {len = 5, num = 257, unum =257, pre = HEX}, 0)
test_vim_str2nr( '0X101G', flags, {len = 5, num = 257, unum =257, pre = HEX}, 6)
test_vim_str2nr('-0X101', flags, {len = 6, num =-257, unum =257, pre = HEX}, 0)
test_vim_str2nr('-0X101', flags, {len = 1, num = 0, unum = 0, pre = 0 }, 1)
test_vim_str2nr('-0X101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 2)
test_vim_str2nr('-0X101', flags, {len = 2, num = 0, unum = 0, pre = 0 }, 3)
test_vim_str2nr('-0X101', flags, {len = 4, num = -1, unum = 1, pre = HEX}, 4)
test_vim_str2nr('-0X101', flags, {len = 5, num = -16, unum = 16, pre = HEX}, 5)
test_vim_str2nr('-0X101', flags, {len = 6, num =-257, unum =257, pre = HEX}, 6)
test_vim_str2nr('-0X101', flags, {len = 6, num =-257, unum =257, pre = HEX}, 7)
test_vim_str2nr('-0X101G', flags, {len = 6, num =-257, unum =257, pre = HEX}, 0)
test_vim_str2nr('-0X101G', flags, {len = 6, num =-257, unum =257, pre = HEX}, 7)
if flags > lib.STR2NR_FORCE then
test_vim_str2nr('-101', flags, {len = 4, num = -257, unum = 257, pre = 0}, 0)
end
end
end)
end)

View File

@@ -1,12 +1,13 @@
local helpers = require('test.unit.helpers')(nil)
local ptr2key = helpers.ptr2key
local cimport = helpers.cimport
local to_cstr = helpers.to_cstr
local ffi = helpers.ffi
local eq = helpers.eq
local eval = cimport('./src/nvim/eval.h', './src/nvim/eval/typval.h',
'./src/nvim/hashtab.h')
'./src/nvim/hashtab.h', './src/nvim/memory.h')
local null_string = {[true]='NULL string'}
local null_list = {[true]='NULL list'}
@@ -23,10 +24,19 @@ local nil_value = {[true]='nil'}
local lua2typvalt
local function tv_list_item_alloc()
return ffi.cast('listitem_T*', eval.xmalloc(ffi.sizeof('listitem_T')))
end
local function tv_list_item_free(li)
eval.tv_clear(li.li_tv)
eval.xfree(li)
end
local function li_alloc(nogc)
local gcfunc = eval.tv_list_item_free
local gcfunc = tv_list_item_free
if nogc then gcfunc = nil end
local li = ffi.gc(eval.tv_list_item_alloc(), gcfunc)
local li = ffi.gc(tv_list_item_alloc(), gcfunc)
li.li_next = nil
li.li_prev = nil
li.li_tv = {v_type=eval.VAR_UNKNOWN, v_lock=eval.VAR_UNLOCKED}
@@ -40,7 +50,7 @@ local function populate_list(l, lua_l, processed)
processed[lua_l] = l
for i = 1, #lua_l do
local item_tv = ffi.gc(lua2typvalt(lua_l[i], processed), nil)
local item_li = eval.tv_list_item_alloc()
local item_li = tv_list_item_alloc()
item_li.li_tv = item_tv
eval.tv_list_append(l, item_li)
end
@@ -91,10 +101,6 @@ local function populate_partial(pt, lua_pt, processed)
return pt
end
local ptr2key = function(ptr)
return tostring(ptr)
end
local lst2tbl
local dct2tbl
@@ -306,7 +312,7 @@ local lua2typvalt_type_tab = {
processed[l].lv_refcount = processed[l].lv_refcount + 1
return typvalt(eval.VAR_LIST, {v_list=processed[l]})
end
local lst = populate_list(eval.tv_list_alloc(), l, processed)
local lst = populate_list(eval.tv_list_alloc(#l), l, processed)
return typvalt(eval.VAR_LIST, {v_list=lst})
end,
[dict_type] = function(l, processed)
@@ -427,7 +433,8 @@ local function int(n)
end
local function list(...)
return populate_list(ffi.gc(eval.tv_list_alloc(), eval.tv_list_unref),
return populate_list(ffi.gc(eval.tv_list_alloc(select('#', ...)),
eval.tv_list_unref),
{...}, {})
end
@@ -536,6 +543,7 @@ return {
typvalt=typvalt,
li_alloc=li_alloc,
tv_list_item_free=tv_list_item_free,
dict_iter=dict_iter,
list_iter=list_iter,

View File

@@ -41,6 +41,7 @@ local tbl2callback = eval_helpers.tbl2callback
local dict_watchers = eval_helpers.dict_watchers
local concat_tables = global_helpers.concat_tables
local map = global_helpers.map
local lib = cimport('./src/nvim/eval/typval.h', './src/nvim/memory.h',
'./src/nvim/mbyte.h', './src/nvim/garray.h',
@@ -80,8 +81,6 @@ local function get_alloc_rets(exp_log, res)
return exp_log
end
local to_cstr_nofree = function(v) return lib.xstrdup(v) end
local alloc_log = alloc_log_new()
before_each(function()
@@ -121,87 +120,6 @@ end
describe('typval.c', function()
describe('list', function()
describe('item', function()
describe('alloc()/free()', function()
itp('works', function()
local li = li_alloc(true)
neq(nil, li)
lib.tv_list_item_free(li)
alloc_log:check({
a.li(li),
a.freed(li),
})
end)
itp('also frees the value', function()
local li
local s
local l
local tv
li = li_alloc(true)
li.li_tv.v_type = lib.VAR_NUMBER
li.li_tv.vval.v_number = 10
lib.tv_list_item_free(li)
alloc_log:check({
a.li(li),
a.freed(li),
})
li = li_alloc(true)
li.li_tv.v_type = lib.VAR_FLOAT
li.li_tv.vval.v_float = 10.5
lib.tv_list_item_free(li)
alloc_log:check({
a.li(li),
a.freed(li),
})
li = li_alloc(true)
li.li_tv.v_type = lib.VAR_STRING
li.li_tv.vval.v_string = nil
lib.tv_list_item_free(li)
alloc_log:check({
a.li(li),
a.freed(alloc_log.null),
a.freed(li),
})
li = li_alloc(true)
li.li_tv.v_type = lib.VAR_STRING
s = to_cstr_nofree('test')
li.li_tv.vval.v_string = s
lib.tv_list_item_free(li)
alloc_log:check({
a.li(li),
a.str(s, #('test')),
a.freed(s),
a.freed(li),
})
li = li_alloc(true)
li.li_tv.v_type = lib.VAR_LIST
l = ffi.gc(list(), nil)
l.lv_refcount = 2
li.li_tv.vval.v_list = l
lib.tv_list_item_free(li)
alloc_log:check({
a.li(li),
a.list(l),
a.freed(li),
})
eq(1, l.lv_refcount)
li = li_alloc(true)
tv = lua2typvalt({})
tv.vval.v_dict.dv_refcount = 2
li.li_tv = tv
lib.tv_list_item_free(li)
alloc_log:check({
a.li(li),
a.dict(tv.vval.v_dict),
a.freed(li),
})
eq(1, tv.vval.v_dict.dv_refcount)
end)
end)
describe('remove()', function()
itp('works', function()
local l = list(1, 2, 3, 4, 5, 6, 7)
@@ -218,24 +136,63 @@ describe('typval.c', function()
a.li(lis[7]),
})
lib.tv_list_item_remove(l, lis[1])
eq(lis[2], lib.tv_list_item_remove(l, lis[1]))
alloc_log:check({
a.freed(table.remove(lis, 1)),
})
eq(lis, list_items(l))
lib.tv_list_item_remove(l, lis[6])
eq(lis[7], lib.tv_list_item_remove(l, lis[6]))
alloc_log:check({
a.freed(table.remove(lis)),
})
eq(lis, list_items(l))
lib.tv_list_item_remove(l, lis[3])
eq(lis[4], lib.tv_list_item_remove(l, lis[3]))
alloc_log:check({
a.freed(table.remove(lis, 3)),
})
eq(lis, list_items(l))
end)
itp('also frees the value', function()
local l = list('a', 'b', 'c', 'd')
neq(nil, l)
local lis = list_items(l)
alloc_log:check({
a.list(l),
a.str(lis[1].li_tv.vval.v_string, 1),
a.li(lis[1]),
a.str(lis[2].li_tv.vval.v_string, 1),
a.li(lis[2]),
a.str(lis[3].li_tv.vval.v_string, 1),
a.li(lis[3]),
a.str(lis[4].li_tv.vval.v_string, 1),
a.li(lis[4]),
})
local strings = map(function(li) return li.li_tv.vval.v_string end,
lis)
eq(lis[2], lib.tv_list_item_remove(l, lis[1]))
alloc_log:check({
a.freed(table.remove(strings, 1)),
a.freed(table.remove(lis, 1)),
})
eq(lis, list_items(l))
eq(lis[3], lib.tv_list_item_remove(l, lis[2]))
alloc_log:check({
a.freed(table.remove(strings, 2)),
a.freed(table.remove(lis, 2)),
})
eq(lis, list_items(l))
eq(nil, lib.tv_list_item_remove(l, lis[2]))
alloc_log:check({
a.freed(table.remove(strings, 2)),
a.freed(table.remove(lis, 2)),
})
eq(lis, list_items(l))
end)
itp('works and adjusts watchers correctly', function()
local l = ffi.gc(list(1, 2, 3, 4, 5, 6, 7), nil)
neq(nil, l)
@@ -257,19 +214,19 @@ describe('typval.c', function()
a.li(lis[7]),
})
lib.tv_list_item_remove(l, lis[4])
eq(lis[5], lib.tv_list_item_remove(l, lis[4]))
alloc_log:check({a.freed(lis[4])})
eq({lis[1], lis[5], lis[7]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item})
lib.tv_list_item_remove(l, lis[2])
eq(lis[3], lib.tv_list_item_remove(l, lis[2]))
alloc_log:check({a.freed(lis[2])})
eq({lis[1], lis[5], lis[7]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item})
lib.tv_list_item_remove(l, lis[7])
eq(nil, lib.tv_list_item_remove(l, lis[7]))
alloc_log:check({a.freed(lis[7])})
eq({lis[1], lis[5], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
lib.tv_list_item_remove(l, lis[1])
eq(lis[3], lib.tv_list_item_remove(l, lis[1]))
alloc_log:check({a.freed(lis[1])})
eq({lis[3], lis[5], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
@@ -449,7 +406,7 @@ describe('typval.c', function()
})
end)
end)
describe('remove_items()', function()
describe('drop_items()', function()
itp('works', function()
local l_tv = lua2typvalt({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13})
local l = l_tv.vval.v_list
@@ -462,21 +419,92 @@ describe('typval.c', function()
}
alloc_log:clear()
lib.tv_list_remove_items(l, lis[1], lis[3])
lib.tv_list_drop_items(l, lis[1], lis[3])
eq({4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, typvalt2lua(l_tv))
eq({lis[4], lis[7], lis[13]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item})
lib.tv_list_remove_items(l, lis[11], lis[13])
lib.tv_list_drop_items(l, lis[11], lis[13])
eq({4, 5, 6, 7, 8, 9, 10}, typvalt2lua(l_tv))
eq({lis[4], lis[7], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
lib.tv_list_remove_items(l, lis[6], lis[8])
lib.tv_list_drop_items(l, lis[6], lis[8])
eq({4, 5, 9, 10}, typvalt2lua(l_tv))
eq({lis[4], lis[9], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
lib.tv_list_drop_items(l, lis[4], lis[10])
eq(empty_list, typvalt2lua(l_tv))
eq({true, true, true}, {lws[1].lw_item == nil, lws[2].lw_item == nil, lws[3].lw_item == nil})
lib.tv_list_watch_remove(l, lws[1])
lib.tv_list_watch_remove(l, lws[2])
lib.tv_list_watch_remove(l, lws[3])
alloc_log:check({})
end)
end)
describe('remove_items()', function()
itp('works', function()
local l_tv = lua2typvalt({'1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13'})
local l = l_tv.vval.v_list
local lis = list_items(l)
local strings = map(function(li) return li.li_tv.vval.v_string end, lis)
-- Three watchers: pointing to first, middle and last elements.
local lws = {
list_watch(l, lis[1]),
list_watch(l, lis[7]),
list_watch(l, lis[13]),
}
alloc_log:clear()
lib.tv_list_remove_items(l, lis[1], lis[3])
eq({'4', '5', '6', '7', '8', '9', '10', '11', '12', '13'}, typvalt2lua(l_tv))
eq({lis[4], lis[7], lis[13]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item})
alloc_log:check({
a.freed(strings[1]),
a.freed(lis[1]),
a.freed(strings[2]),
a.freed(lis[2]),
a.freed(strings[3]),
a.freed(lis[3]),
})
lib.tv_list_remove_items(l, lis[11], lis[13])
eq({'4', '5', '6', '7', '8', '9', '10'}, typvalt2lua(l_tv))
eq({lis[4], lis[7], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
alloc_log:check({
a.freed(strings[11]),
a.freed(lis[11]),
a.freed(strings[12]),
a.freed(lis[12]),
a.freed(strings[13]),
a.freed(lis[13]),
})
lib.tv_list_remove_items(l, lis[6], lis[8])
eq({'4', '5', '9', '10'}, typvalt2lua(l_tv))
eq({lis[4], lis[9], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil})
alloc_log:check({
a.freed(strings[6]),
a.freed(lis[6]),
a.freed(strings[7]),
a.freed(lis[7]),
a.freed(strings[8]),
a.freed(lis[8]),
})
lib.tv_list_remove_items(l, lis[4], lis[10])
eq(empty_list, typvalt2lua(l_tv))
eq({true, true, true}, {lws[1].lw_item == nil, lws[2].lw_item == nil, lws[3].lw_item == nil})
alloc_log:check({
a.freed(strings[4]),
a.freed(lis[4]),
a.freed(strings[5]),
a.freed(lis[5]),
a.freed(strings[9]),
a.freed(lis[9]),
a.freed(strings[10]),
a.freed(lis[10]),
})
lib.tv_list_watch_remove(l, lws[1])
lib.tv_list_watch_remove(l, lws[2])
@@ -678,6 +706,66 @@ describe('typval.c', function()
eq({int(-100500), int(100500)}, typvalt2lua(l_tv))
end)
end)
describe('tv()', function()
itp('works', function()
local l_tv = lua2typvalt(empty_list)
local l = l_tv.vval.v_list
local l_l_tv = lua2typvalt(empty_list)
alloc_log:clear()
local l_l = l_l_tv.vval.v_list
eq(1, l_l.lv_refcount)
lib.tv_list_append_tv(l, l_l_tv)
eq(2, l_l.lv_refcount)
eq(l_l, l.lv_first.li_tv.vval.v_list)
alloc_log:check({
a.li(l.lv_first),
})
local l_s_tv = lua2typvalt('test')
alloc_log:check({
a.str(l_s_tv.vval.v_string, 'test'),
})
lib.tv_list_append_tv(l, l_s_tv)
alloc_log:check({
a.li(l.lv_last),
a.str(l.lv_last.li_tv.vval.v_string, 'test'),
})
eq({empty_list, 'test'}, typvalt2lua(l_tv))
end)
end)
describe('owned tv()', function()
itp('works', function()
local l_tv = lua2typvalt(empty_list)
local l = l_tv.vval.v_list
local l_l_tv = lua2typvalt(empty_list)
alloc_log:clear()
local l_l = l_l_tv.vval.v_list
eq(1, l_l.lv_refcount)
lib.tv_list_append_owned_tv(l, l_l_tv)
eq(1, l_l.lv_refcount)
l_l.lv_refcount = l_l.lv_refcount + 1
eq(l_l, l.lv_first.li_tv.vval.v_list)
alloc_log:check({
a.li(l.lv_first),
})
local l_s_tv = ffi.gc(lua2typvalt('test'), nil)
alloc_log:check({
a.str(l_s_tv.vval.v_string, 'test'),
})
lib.tv_list_append_owned_tv(l, l_s_tv)
eq(l_s_tv.vval.v_string, l.lv_last.li_tv.vval.v_string)
l_s_tv.vval.v_string = nil
alloc_log:check({
a.li(l.lv_last),
})
eq({empty_list, 'test'}, typvalt2lua(l_tv))
end)
end)
end)
describe('copy()', function()
local function tv_list_copy(...)
@@ -2319,7 +2407,7 @@ describe('typval.c', function()
describe('list ret()', function()
itp('works', function()
local rettv = typvalt(lib.VAR_UNKNOWN)
local l = lib.tv_list_alloc_ret(rettv)
local l = lib.tv_list_alloc_ret(rettv, 0)
eq(empty_list, typvalt2lua(rettv))
eq(rettv.vval.v_list, l)
end)

View File

@@ -65,11 +65,12 @@ local tokens = P { "tokens";
identifier = Ct(C(R("az","AZ","__") * R("09","az","AZ","__")^0) * Cc"identifier"),
-- Single character in a string
string_char = R("az","AZ","09") + S"$%^&*()_-+={[}]:;@~#<,>.!?/ \t" + (P"\\" * S[[ntvbrfa\?'"0x]]),
sstring_char = R("\001&","([","]\255") + (P"\\" * S[[ntvbrfa\?'"0x]]),
dstring_char = R("\001!","#[","]\255") + (P"\\" * S[[ntvbrfa\?'"0x]]),
-- String literal
string = Ct(C(P"'" * (V"string_char" + P'"')^0 * P"'" +
P'"' * (V"string_char" + P"'")^0 * P'"') * Cc"string"),
string = Ct(C(P"'" * (V"sstring_char" + P'"')^0 * P"'" +
P'"' * (V"dstring_char" + P"'")^0 * P'"') * Cc"string"),
-- Operator
operator = Ct(C(P">>=" + P"<<=" + P"..." +

View File

@@ -138,7 +138,7 @@ local function filter_complex_blocks(body)
for line in body:gmatch("[^\r\n]+") do
if not (string.find(line, "(^)", 1, true) ~= nil
or string.find(line, "_ISwupper", 1, true)
or string.find(line, "_Float128")
or string.find(line, "_Float")
or string.find(line, "msgpack_zone_push_finalizer")
or string.find(line, "msgpack_unpacker_reserve_buffer")
or string.find(line, "UUID_NULL") -- static const uuid_t UUID_NULL = {...}
@@ -316,7 +316,7 @@ local function alloc_log_new()
eq(exp, self.log)
self:clear()
end
function log:clear_tmp_allocs()
function log:clear_tmp_allocs(clear_null_frees)
local toremove = {}
local allocs = {}
for i, v in ipairs(self.log) do
@@ -328,6 +328,8 @@ local function alloc_log_new()
if v.func == 'free' then
toremove[#toremove + 1] = i
end
elseif clear_null_frees and v.args[1] == self.null then
toremove[#toremove + 1] = i
end
if v.func == 'realloc' then
allocs[tostring(v.ret)] = i
@@ -528,9 +530,13 @@ local hook_numlen = 5
local hook_msglen = 1 + 1 + 1 + (1 + hook_fnamelen) + (1 + hook_sfnamelen) + (1 + hook_numlen) + 1
local tracehelp = dedent([[
Trace: either in the format described below or custom debug output starting
with `>`. Latter lines still have the same width in byte.
┌ Trace type: _r_eturn from function , function _c_all, _l_ine executed,
│ _t_ail return, _C_ount (should not actually appear),
│ _s_aved from previous run for reference.
│ _s_aved from previous run for reference, _>_ for custom debug
│ output.
│┏ Function type: _L_ua function, _C_ function, _m_ain part of chunk,
│┃ function that did _t_ail call.
│┃┌ Function name type: _g_lobal, _l_ocal, _m_ethod, _f_ield, _u_pvalue,
@@ -628,14 +634,24 @@ end
local trace_end_msg = ('E%s\n'):format((' '):rep(hook_msglen - 2))
local _debug_log
local debug_log = only_separate(function(...)
return _debug_log(...)
end)
local function itp_child(wr, func)
init()
collectgarbage('stop')
child_sethook(wr)
local err, emsg = pcall(func)
collectgarbage('restart')
collectgarbage()
debug.sethook()
_debug_log = function(s)
s = s:sub(1, hook_msglen - 2)
sc.write(wr, '>' .. s .. (' '):rep(hook_msglen - 2 - #s) .. '\n')
end
local err, emsg = pcall(init)
if err then
collectgarbage('stop')
child_sethook(wr)
err, emsg = pcall(func)
debug.sethook()
end
emsg = tostring(emsg)
sc.write(wr, trace_end_msg)
if not err then
@@ -644,19 +660,21 @@ local function itp_child(wr, func)
end
sc.write(wr, ('-\n%05u\n%s'):format(#emsg, emsg))
deinit()
sc.close(wr)
sc.exit(1)
else
sc.write(wr, '+\n')
deinit()
sc.close(wr)
sc.exit(0)
end
collectgarbage('restart')
collectgarbage()
sc.write(wr, '$\n')
sc.close(wr)
sc.exit(err and 0 or 1)
end
local function check_child_err(rd)
local trace = {}
local did_traceline = false
local maxtrace = tonumber(os.getenv('NVIM_TEST_MAXTRACE')) or 1024
while true do
local traceline = sc.read(rd, hook_msglen)
if #traceline ~= hook_msglen then
@@ -671,36 +689,48 @@ local function check_child_err(rd)
break
end
trace[#trace + 1] = traceline
if #trace > maxtrace then
table.remove(trace, 1)
end
end
local res = sc.read(rd, 2)
if #res ~= 2 then
local error
if #trace == 0 then
error = '\nTest crashed, no trace available\n'
else
error = '\nTest crashed, trace:\n' .. tracehelp
for i = 1, #trace do
error = error .. trace[i]
if #res == 2 then
local err = ''
if res ~= '+\n' then
eq('-\n', res)
local len_s = sc.read(rd, 5)
local len = tonumber(len_s)
neq(0, len)
if os.getenv('NVIM_TEST_TRACE_ON_ERROR') == '1' and #trace ~= 0 then
err = '\nTest failed, trace:\n' .. tracehelp
for _, traceline in ipairs(trace) do
err = err .. traceline
end
end
err = err .. sc.read(rd, len + 1)
end
local eres = sc.read(rd, 2)
if eres ~= '$\n' then
if #trace == 0 then
err = '\nTest crashed, no trace available\n'
else
err = '\nTest crashed, trace:\n' .. tracehelp
for i = 1, #trace do
err = err .. trace[i]
end
end
if not did_traceline then
err = err .. '\nNo end of trace occurred'
end
local cc_err, cc_emsg = pcall(check_cores, Paths.test_luajit_prg, true)
if not cc_err then
err = err .. '\ncheck_cores failed: ' .. cc_emsg
end
end
if not did_traceline then
error = error .. '\nNo end of trace occurred'
if err ~= '' then
assert.just_fail(err)
end
local cc_err, cc_emsg = pcall(check_cores, Paths.test_luajit_prg, true)
if not cc_err then
error = error .. '\ncheck_cores failed: ' .. cc_emsg
end
assert.just_fail(error)
end
if res == '+\n' then
return
end
eq('-\n', res)
local len_s = sc.read(rd, 5)
local len = tonumber(len_s)
neq(0, len)
local err = sc.read(rd, len + 1)
assert.just_fail(err)
end
local function itp_parent(rd, pid, allow_failure)
@@ -754,6 +784,60 @@ end
cimport('./src/nvim/types.h', './src/nvim/main.h', './src/nvim/os/time.h')
local function conv_enum(etab, eval)
local n = tonumber(eval)
return etab[n] or n
end
local function array_size(arr)
return ffi.sizeof(arr) / ffi.sizeof(arr[0])
end
local function kvi_size(kvi)
return array_size(kvi.init_array)
end
local function kvi_init(kvi)
kvi.capacity = kvi_size(kvi)
kvi.items = kvi.init_array
return kvi
end
local function kvi_destroy(kvi)
if kvi.items ~= kvi.init_array then
lib.xfree(kvi.items)
end
end
local function kvi_new(ct)
return kvi_init(ffi.new(ct))
end
local function make_enum_conv_tab(m, values, skip_pref, set_cb)
child_call_once(function()
local ret = {}
for _, v in ipairs(values) do
local str_v = v
if v:sub(1, #skip_pref) == skip_pref then
str_v = v:sub(#skip_pref + 1)
end
ret[tonumber(m[v])] = str_v
end
set_cb(ret)
end)
end
local function ptr2addr(ptr)
return tonumber(ffi.cast('intptr_t', ffi.cast('void *', ptr)))
end
local s = ffi.new('char[64]', {0})
local function ptr2key(ptr)
ffi.C.snprintf(s, ffi.sizeof(s), '%p', ffi.cast('void *', ptr))
return ffi.string(s)
end
local module = {
cimport = cimport,
cppimport = cppimport,
@@ -774,6 +858,16 @@ local module = {
child_call_once = child_call_once,
child_cleanup_once = child_cleanup_once,
sc = sc,
conv_enum = conv_enum,
array_size = array_size,
kvi_destroy = kvi_destroy,
kvi_size = kvi_size,
kvi_init = kvi_init,
kvi_new = kvi_new,
make_enum_conv_tab = make_enum_conv_tab,
ptr2addr = ptr2addr,
ptr2key = ptr2key,
debug_log = debug_log,
}
return function()
return module

71
test/unit/keymap_spec.lua Normal file
View File

@@ -0,0 +1,71 @@
local helpers = require("test.unit.helpers")(after_each)
local itp = helpers.gen_itp(it)
local ffi = helpers.ffi
local eq = helpers.eq
local neq = helpers.neq
local keymap = helpers.cimport("./src/nvim/keymap.h")
describe('keymap.c', function()
describe('find_special_key()', function()
local srcp = ffi.new('const unsigned char *[1]')
local modp = ffi.new('int[1]')
itp('no keycode', function()
srcp[0] = 'abc'
eq(0, keymap.find_special_key(srcp, 3, modp, false, false, false))
end)
itp('keycode with multiple modifiers', function()
srcp[0] = '<C-M-S-A>'
neq(0, keymap.find_special_key(srcp, 9, modp, false, false, false))
neq(0, modp[0])
end)
itp('case-insensitive', function()
-- Compare other capitalizations to this.
srcp[0] = '<C-A>'
local all_caps_key =
keymap.find_special_key(srcp, 5, modp, false, false, false)
local all_caps_mod = modp[0]
srcp[0] = '<C-a>'
eq(all_caps_key,
keymap.find_special_key(srcp, 5, modp, false, false, false))
eq(all_caps_mod, modp[0])
srcp[0] = '<c-A>'
eq(all_caps_key,
keymap.find_special_key(srcp, 5, modp, false, false, false))
eq(all_caps_mod, modp[0])
srcp[0] = '<c-a>'
eq(all_caps_key,
keymap.find_special_key(srcp, 5, modp, false, false, false))
eq(all_caps_mod, modp[0])
end)
itp('double-quote in keycode #7411', function()
-- Unescaped with in_string=false
srcp[0] = '<C-">'
eq(string.byte('"'),
keymap.find_special_key(srcp, 5, modp, false, false, false))
-- Unescaped with in_string=true
eq(0, keymap.find_special_key(srcp, 5, modp, false, false, true))
-- Escaped with in_string=false
srcp[0] = '<C-\\">'
-- Should fail because the key is invalid
-- (more than 1 non-modifier character).
eq(0, keymap.find_special_key(srcp, 6, modp, false, false, false))
-- Escaped with in_string=true
eq(string.byte('"'),
keymap.find_special_key(srcp, 6, modp, false, false, true))
end)
end)
end)

View File

@@ -199,7 +199,7 @@ describe('fs.c', function()
itp('returns the absolute path when given an executable inside $PATH', function()
local fullpath = exe('ls')
eq(1, fs.path_is_absolute_path(to_cstr(fullpath)))
eq(1, fs.path_is_absolute(to_cstr(fullpath)))
end)
itp('returns the absolute path when given an executable relative to the current dir', function()

View File

@@ -261,7 +261,7 @@ describe('path.c', function()
end)
end)
describe('path_shorten_fname_if_possible', function()
describe('path_try_shorten_fname', function()
local cwd = lfs.currentdir()
before_each(function()
@@ -273,22 +273,22 @@ describe('path_shorten_fname_if_possible', function()
lfs.rmdir('ut_directory')
end)
describe('path_shorten_fname_if_possible', function()
describe('path_try_shorten_fname', function()
itp('returns shortened path if possible', function()
lfs.chdir('ut_directory')
local full = to_cstr(lfs.currentdir() .. '/subdir/file.txt')
eq('subdir/file.txt', (ffi.string(cimp.path_shorten_fname_if_possible(full))))
eq('subdir/file.txt', (ffi.string(cimp.path_try_shorten_fname(full))))
end)
itp('returns `full_path` if a shorter version is not possible', function()
local old = lfs.currentdir()
lfs.chdir('ut_directory')
local full = old .. '/subdir/file.txt'
eq(full, (ffi.string(cimp.path_shorten_fname_if_possible(to_cstr(full)))))
eq(full, (ffi.string(cimp.path_try_shorten_fname(to_cstr(full)))))
end)
itp('returns NULL if `full_path` is NULL', function()
eq(NULL, (cimp.path_shorten_fname_if_possible(NULL)))
eq(NULL, (cimp.path_try_shorten_fname(NULL)))
end)
end)
end)
@@ -585,22 +585,22 @@ describe('path.c', function()
end)
end)
describe('path_is_absolute_path', function()
local function path_is_absolute_path(filename)
describe('path_is_absolute', function()
local function path_is_absolute(filename)
filename = to_cstr(filename)
return cimp.path_is_absolute_path(filename)
return cimp.path_is_absolute(filename)
end
itp('returns true if filename starts with a slash', function()
eq(OK, path_is_absolute_path('/some/directory/'))
eq(OK, path_is_absolute('/some/directory/'))
end)
itp('returns true if filename starts with a tilde', function()
eq(OK, path_is_absolute_path('~/in/my/home~/directory'))
eq(OK, path_is_absolute('~/in/my/home~/directory'))
end)
itp('returns false if filename starts not with slash nor tilde', function()
eq(FAIL, path_is_absolute_path('not/in/my/home~/directory'))
eq(FAIL, path_is_absolute('not/in/my/home~/directory'))
end)
end)
end)

View File

@@ -0,0 +1,428 @@
local helpers = require('test.unit.helpers')(after_each)
local global_helpers = require('test.helpers')
local itp = helpers.gen_itp(it)
local viml_helpers = require('test.unit.viml.helpers')
local child_call_once = helpers.child_call_once
local conv_enum = helpers.conv_enum
local cimport = helpers.cimport
local ffi = helpers.ffi
local eq = helpers.eq
local conv_ccs = viml_helpers.conv_ccs
local new_pstate = viml_helpers.new_pstate
local conv_cmp_type = viml_helpers.conv_cmp_type
local pstate_set_str = viml_helpers.pstate_set_str
local conv_expr_asgn_type = viml_helpers.conv_expr_asgn_type
local shallowcopy = global_helpers.shallowcopy
local intchar2lua = global_helpers.intchar2lua
local lib = cimport('./src/nvim/viml/parser/expressions.h')
local eltkn_type_tab, eltkn_mul_type_tab, eltkn_opt_scope_tab
child_call_once(function()
eltkn_type_tab = {
[tonumber(lib.kExprLexInvalid)] = 'Invalid',
[tonumber(lib.kExprLexMissing)] = 'Missing',
[tonumber(lib.kExprLexSpacing)] = 'Spacing',
[tonumber(lib.kExprLexEOC)] = 'EOC',
[tonumber(lib.kExprLexQuestion)] = 'Question',
[tonumber(lib.kExprLexColon)] = 'Colon',
[tonumber(lib.kExprLexOr)] = 'Or',
[tonumber(lib.kExprLexAnd)] = 'And',
[tonumber(lib.kExprLexComparison)] = 'Comparison',
[tonumber(lib.kExprLexPlus)] = 'Plus',
[tonumber(lib.kExprLexMinus)] = 'Minus',
[tonumber(lib.kExprLexDot)] = 'Dot',
[tonumber(lib.kExprLexMultiplication)] = 'Multiplication',
[tonumber(lib.kExprLexNot)] = 'Not',
[tonumber(lib.kExprLexNumber)] = 'Number',
[tonumber(lib.kExprLexSingleQuotedString)] = 'SingleQuotedString',
[tonumber(lib.kExprLexDoubleQuotedString)] = 'DoubleQuotedString',
[tonumber(lib.kExprLexOption)] = 'Option',
[tonumber(lib.kExprLexRegister)] = 'Register',
[tonumber(lib.kExprLexEnv)] = 'Env',
[tonumber(lib.kExprLexPlainIdentifier)] = 'PlainIdentifier',
[tonumber(lib.kExprLexBracket)] = 'Bracket',
[tonumber(lib.kExprLexFigureBrace)] = 'FigureBrace',
[tonumber(lib.kExprLexParenthesis)] = 'Parenthesis',
[tonumber(lib.kExprLexComma)] = 'Comma',
[tonumber(lib.kExprLexArrow)] = 'Arrow',
[tonumber(lib.kExprLexAssignment)] = 'Assignment',
}
eltkn_mul_type_tab = {
[tonumber(lib.kExprLexMulMul)] = 'Mul',
[tonumber(lib.kExprLexMulDiv)] = 'Div',
[tonumber(lib.kExprLexMulMod)] = 'Mod',
}
eltkn_opt_scope_tab = {
[tonumber(lib.kExprOptScopeUnspecified)] = 'Unspecified',
[tonumber(lib.kExprOptScopeGlobal)] = 'Global',
[tonumber(lib.kExprOptScopeLocal)] = 'Local',
}
end)
local function conv_eltkn_type(typ)
return conv_enum(eltkn_type_tab, typ)
end
local bracket_types = {
Bracket = true,
FigureBrace = true,
Parenthesis = true,
}
local function eltkn2lua(pstate, tkn)
local ret = {
type = conv_eltkn_type(tkn.type),
}
pstate_set_str(pstate, tkn.start, tkn.len, ret)
if not ret.error and (#(ret.str) ~= ret.len) then
ret.error = '#str /= len'
end
if ret.type == 'Comparison' then
ret.data = {
type = conv_cmp_type(tkn.data.cmp.type),
ccs = conv_ccs(tkn.data.cmp.ccs),
inv = (not not tkn.data.cmp.inv),
}
elseif ret.type == 'Multiplication' then
ret.data = { type = conv_enum(eltkn_mul_type_tab, tkn.data.mul.type) }
elseif bracket_types[ret.type] then
ret.data = { closing = (not not tkn.data.brc.closing) }
elseif ret.type == 'Register' then
ret.data = { name = intchar2lua(tkn.data.reg.name) }
elseif (ret.type == 'SingleQuotedString'
or ret.type == 'DoubleQuotedString') then
ret.data = { closed = (not not tkn.data.str.closed) }
elseif ret.type == 'Option' then
ret.data = {
scope = conv_enum(eltkn_opt_scope_tab, tkn.data.opt.scope),
name = ffi.string(tkn.data.opt.name, tkn.data.opt.len),
}
elseif ret.type == 'PlainIdentifier' then
ret.data = {
scope = intchar2lua(tkn.data.var.scope),
autoload = (not not tkn.data.var.autoload),
}
elseif ret.type == 'Number' then
ret.data = {
is_float = (not not tkn.data.num.is_float),
base = tonumber(tkn.data.num.base),
}
ret.data.val = tonumber(tkn.data.num.is_float
and tkn.data.num.val.floating
or tkn.data.num.val.integer)
elseif ret.type == 'Assignment' then
ret.data = { type = conv_expr_asgn_type(tkn.data.ass.type) }
elseif ret.type == 'Invalid' then
ret.data = { error = ffi.string(tkn.data.err.msg) }
end
return ret, tkn
end
local function next_eltkn(pstate, flags)
return eltkn2lua(pstate, lib.viml_pexpr_next_token(pstate, flags))
end
describe('Expressions lexer', function()
local flags = 0
local should_advance = true
local function check_advance(pstate, bytes_to_advance, initial_col)
local tgt = initial_col + bytes_to_advance
if should_advance then
if pstate.reader.lines.items[0].size == tgt then
eq(1, pstate.pos.line)
eq(0, pstate.pos.col)
else
eq(0, pstate.pos.line)
eq(tgt, pstate.pos.col)
end
else
eq(0, pstate.pos.line)
eq(initial_col, pstate.pos.col)
end
end
local function singl_eltkn_test(typ, str, data)
local pstate = new_pstate({str})
eq({data=data, len=#str, start={col=0, line=0}, str=str, type=typ},
next_eltkn(pstate, flags))
check_advance(pstate, #str, 0)
if not (
typ == 'Spacing'
or (typ == 'Register' and str == '@')
or ((typ == 'SingleQuotedString' or typ == 'DoubleQuotedString')
and not data.closed)
) then
pstate = new_pstate({str .. ' '})
eq({data=data, len=#str, start={col=0, line=0}, str=str, type=typ},
next_eltkn(pstate, flags))
check_advance(pstate, #str, 0)
end
pstate = new_pstate({'x' .. str})
pstate.pos.col = 1
eq({data=data, len=#str, start={col=1, line=0}, str=str, type=typ},
next_eltkn(pstate, flags))
check_advance(pstate, #str, 1)
end
local function scope_test(scope)
singl_eltkn_test('PlainIdentifier', scope .. ':test#var', {autoload=true, scope=scope})
singl_eltkn_test('PlainIdentifier', scope .. ':', {autoload=false, scope=scope})
end
local function comparison_test(op, inv_op, cmp_type)
singl_eltkn_test('Comparison', op, {type=cmp_type, inv=false, ccs='UseOption'})
singl_eltkn_test('Comparison', inv_op, {type=cmp_type, inv=true, ccs='UseOption'})
singl_eltkn_test('Comparison', op .. '#', {type=cmp_type, inv=false, ccs='MatchCase'})
singl_eltkn_test('Comparison', inv_op .. '#', {type=cmp_type, inv=true, ccs='MatchCase'})
singl_eltkn_test('Comparison', op .. '?', {type=cmp_type, inv=false, ccs='IgnoreCase'})
singl_eltkn_test('Comparison', inv_op .. '?', {type=cmp_type, inv=true, ccs='IgnoreCase'})
end
local function simple_test(pstate_arg, exp_type, exp_len, exp)
local pstate = new_pstate(pstate_arg)
exp = shallowcopy(exp)
exp.type = exp_type
exp.len = exp_len or #(pstate_arg[0])
exp.start = { col = 0, line = 0 }
eq(exp, next_eltkn(pstate, flags))
end
local function stable_tests()
singl_eltkn_test('Parenthesis', '(', {closing=false})
singl_eltkn_test('Parenthesis', ')', {closing=true})
singl_eltkn_test('Bracket', '[', {closing=false})
singl_eltkn_test('Bracket', ']', {closing=true})
singl_eltkn_test('FigureBrace', '{', {closing=false})
singl_eltkn_test('FigureBrace', '}', {closing=true})
singl_eltkn_test('Question', '?')
singl_eltkn_test('Colon', ':')
singl_eltkn_test('Dot', '.')
singl_eltkn_test('Assignment', '.=', {type='Concat'})
singl_eltkn_test('Plus', '+')
singl_eltkn_test('Assignment', '+=', {type='Add'})
singl_eltkn_test('Comma', ',')
singl_eltkn_test('Multiplication', '*', {type='Mul'})
singl_eltkn_test('Multiplication', '/', {type='Div'})
singl_eltkn_test('Multiplication', '%', {type='Mod'})
singl_eltkn_test('Spacing', ' \t\t \t\t')
singl_eltkn_test('Spacing', ' ')
singl_eltkn_test('Spacing', '\t')
singl_eltkn_test('Invalid', '\x01\x02\x03', {error='E15: Invalid control character present in input: %.*s'})
singl_eltkn_test('Number', '0123', {is_float=false, base=8, val=83})
singl_eltkn_test('Number', '01234567', {is_float=false, base=8, val=342391})
singl_eltkn_test('Number', '012345678', {is_float=false, base=10, val=12345678})
singl_eltkn_test('Number', '0x123', {is_float=false, base=16, val=291})
singl_eltkn_test('Number', '0x56FF', {is_float=false, base=16, val=22271})
singl_eltkn_test('Number', '0xabcdef', {is_float=false, base=16, val=11259375})
singl_eltkn_test('Number', '0xABCDEF', {is_float=false, base=16, val=11259375})
singl_eltkn_test('Number', '0x0', {is_float=false, base=16, val=0})
singl_eltkn_test('Number', '00', {is_float=false, base=8, val=0})
singl_eltkn_test('Number', '0b0', {is_float=false, base=2, val=0})
singl_eltkn_test('Number', '0b010111', {is_float=false, base=2, val=23})
singl_eltkn_test('Number', '0b100111', {is_float=false, base=2, val=39})
singl_eltkn_test('Number', '0', {is_float=false, base=10, val=0})
singl_eltkn_test('Number', '9', {is_float=false, base=10, val=9})
singl_eltkn_test('Env', '$abc')
singl_eltkn_test('Env', '$')
singl_eltkn_test('PlainIdentifier', 'test', {autoload=false, scope=0})
singl_eltkn_test('PlainIdentifier', '_test', {autoload=false, scope=0})
singl_eltkn_test('PlainIdentifier', '_test_foo', {autoload=false, scope=0})
singl_eltkn_test('PlainIdentifier', 't', {autoload=false, scope=0})
singl_eltkn_test('PlainIdentifier', 'test5', {autoload=false, scope=0})
singl_eltkn_test('PlainIdentifier', 't0', {autoload=false, scope=0})
singl_eltkn_test('PlainIdentifier', 'test#var', {autoload=true, scope=0})
singl_eltkn_test('PlainIdentifier', 'test#var#val###', {autoload=true, scope=0})
singl_eltkn_test('PlainIdentifier', 't#####', {autoload=true, scope=0})
singl_eltkn_test('And', '&&')
singl_eltkn_test('Or', '||')
singl_eltkn_test('Invalid', '&', {error='E112: Option name missing: %.*s'})
singl_eltkn_test('Option', '&opt', {scope='Unspecified', name='opt'})
singl_eltkn_test('Option', '&t_xx', {scope='Unspecified', name='t_xx'})
singl_eltkn_test('Option', '&t_\r\r', {scope='Unspecified', name='t_\r\r'})
singl_eltkn_test('Option', '&t_\t\t', {scope='Unspecified', name='t_\t\t'})
singl_eltkn_test('Option', '&t_ ', {scope='Unspecified', name='t_ '})
singl_eltkn_test('Option', '&g:opt', {scope='Global', name='opt'})
singl_eltkn_test('Option', '&l:opt', {scope='Local', name='opt'})
singl_eltkn_test('Invalid', '&l:', {error='E112: Option name missing: %.*s'})
singl_eltkn_test('Invalid', '&g:', {error='E112: Option name missing: %.*s'})
singl_eltkn_test('Register', '@', {name=-1})
singl_eltkn_test('Register', '@a', {name='a'})
singl_eltkn_test('Register', '@\r', {name=13})
singl_eltkn_test('Register', '@ ', {name=' '})
singl_eltkn_test('Register', '@\t', {name=9})
singl_eltkn_test('SingleQuotedString', '\'test', {closed=false})
singl_eltkn_test('SingleQuotedString', '\'test\'', {closed=true})
singl_eltkn_test('SingleQuotedString', '\'\'\'\'', {closed=true})
singl_eltkn_test('SingleQuotedString', '\'x\'\'\'', {closed=true})
singl_eltkn_test('SingleQuotedString', '\'\'\'x\'', {closed=true})
singl_eltkn_test('SingleQuotedString', '\'\'\'', {closed=false})
singl_eltkn_test('SingleQuotedString', '\'x\'\'', {closed=false})
singl_eltkn_test('SingleQuotedString', '\'\'\'x', {closed=false})
singl_eltkn_test('DoubleQuotedString', '"test', {closed=false})
singl_eltkn_test('DoubleQuotedString', '"test"', {closed=true})
singl_eltkn_test('DoubleQuotedString', '"\\""', {closed=true})
singl_eltkn_test('DoubleQuotedString', '"x\\""', {closed=true})
singl_eltkn_test('DoubleQuotedString', '"\\"x"', {closed=true})
singl_eltkn_test('DoubleQuotedString', '"\\"', {closed=false})
singl_eltkn_test('DoubleQuotedString', '"x\\"', {closed=false})
singl_eltkn_test('DoubleQuotedString', '"\\"x', {closed=false})
singl_eltkn_test('Not', '!')
singl_eltkn_test('Assignment', '=', {type='Plain'})
comparison_test('==', '!=', 'Equal')
comparison_test('=~', '!~', 'Matches')
comparison_test('>', '<=', 'Greater')
comparison_test('>=', '<', 'GreaterOrEqual')
singl_eltkn_test('Minus', '-')
singl_eltkn_test('Assignment', '-=', {type='Subtract'})
singl_eltkn_test('Arrow', '->')
singl_eltkn_test('Invalid', '~', {error='E15: Unidentified character: %.*s'})
simple_test({{data=nil, size=0}}, 'EOC', 0, {error='start.col >= #pstr'})
simple_test({''}, 'EOC', 0, {error='start.col >= #pstr'})
simple_test({'2.'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'2e5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'2.x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'2.2.'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'2.0x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'2.0e'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'2.0e+'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'2.0e-'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'2.0e+x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'2.0e-x'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'2.0e+1a'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'2.0e-1a'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'0b102'}, 'Number', 4, {data={is_float=false, base=2, val=2}, str='0b10'})
simple_test({'10F'}, 'Number', 2, {data={is_float=false, base=10, val=10}, str='10'})
simple_test({'0x0123456789ABCDEFG'}, 'Number', 18, {data={is_float=false, base=16, val=81985529216486895}, str='0x0123456789ABCDEF'})
simple_test({{data='00', size=2}}, 'Number', 2, {data={is_float=false, base=8, val=0}, str='00'})
simple_test({{data='009', size=2}}, 'Number', 2, {data={is_float=false, base=8, val=0}, str='00'})
simple_test({{data='01', size=1}}, 'Number', 1, {data={is_float=false, base=10, val=0}, str='0'})
end
local function regular_scope_tests()
scope_test('s')
scope_test('g')
scope_test('v')
scope_test('b')
scope_test('w')
scope_test('t')
scope_test('l')
scope_test('a')
simple_test({'g:'}, 'PlainIdentifier', 2, {data={scope='g', autoload=false}, str='g:'})
simple_test({'g:is#foo'}, 'PlainIdentifier', 8, {data={scope='g', autoload=true}, str='g:is#foo'})
simple_test({'g:isnot#foo'}, 'PlainIdentifier', 11, {data={scope='g', autoload=true}, str='g:isnot#foo'})
end
local function regular_is_tests()
comparison_test('is', 'isnot', 'Identical')
simple_test({'is'}, 'Comparison', 2, {data={type='Identical', inv=false, ccs='UseOption'}, str='is'})
simple_test({'isnot'}, 'Comparison', 5, {data={type='Identical', inv=true, ccs='UseOption'}, str='isnot'})
simple_test({'is?'}, 'Comparison', 3, {data={type='Identical', inv=false, ccs='IgnoreCase'}, str='is?'})
simple_test({'isnot?'}, 'Comparison', 6, {data={type='Identical', inv=true, ccs='IgnoreCase'}, str='isnot?'})
simple_test({'is#'}, 'Comparison', 3, {data={type='Identical', inv=false, ccs='MatchCase'}, str='is#'})
simple_test({'isnot#'}, 'Comparison', 6, {data={type='Identical', inv=true, ccs='MatchCase'}, str='isnot#'})
simple_test({'is#foo'}, 'Comparison', 3, {data={type='Identical', inv=false, ccs='MatchCase'}, str='is#'})
simple_test({'isnot#foo'}, 'Comparison', 6, {data={type='Identical', inv=true, ccs='MatchCase'}, str='isnot#'})
end
local function regular_number_tests()
simple_test({'2.0'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'2.0e5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'2.0e+5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({'2.0e-5'}, 'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
end
local function regular_eoc_tests()
singl_eltkn_test('EOC', '|')
singl_eltkn_test('EOC', '\0')
singl_eltkn_test('EOC', '\n')
end
itp('works (single tokens, zero flags)', function()
stable_tests()
regular_eoc_tests()
regular_scope_tests()
regular_is_tests()
regular_number_tests()
end)
itp('peeks', function()
flags = tonumber(lib.kELFlagPeek)
should_advance = false
stable_tests()
regular_eoc_tests()
regular_scope_tests()
regular_is_tests()
regular_number_tests()
end)
itp('forbids scope', function()
flags = tonumber(lib.kELFlagForbidScope)
stable_tests()
regular_eoc_tests()
regular_is_tests()
regular_number_tests()
simple_test({'g:'}, 'PlainIdentifier', 1, {data={scope=0, autoload=false}, str='g'})
end)
itp('allows floats', function()
flags = tonumber(lib.kELFlagAllowFloat)
stable_tests()
regular_eoc_tests()
regular_scope_tests()
regular_is_tests()
simple_test({'2.2'}, 'Number', 3, {data={is_float=true, base=10, val=2.2}, str='2.2'})
simple_test({'2.0e5'}, 'Number', 5, {data={is_float=true, base=10, val=2e5}, str='2.0e5'})
simple_test({'2.0e+5'}, 'Number', 6, {data={is_float=true, base=10, val=2e5}, str='2.0e+5'})
simple_test({'2.0e-5'}, 'Number', 6, {data={is_float=true, base=10, val=2e-5}, str='2.0e-5'})
simple_test({'2.500000e-5'}, 'Number', 11, {data={is_float=true, base=10, val=2.5e-5}, str='2.500000e-5'})
simple_test({'2.5555e2'}, 'Number', 8, {data={is_float=true, base=10, val=2.5555e2}, str='2.5555e2'})
simple_test({'2.5555e+2'}, 'Number', 9, {data={is_float=true, base=10, val=2.5555e2}, str='2.5555e+2'})
simple_test({'2.5555e-2'}, 'Number', 9, {data={is_float=true, base=10, val=2.5555e-2}, str='2.5555e-2'})
simple_test({{data='2.5e-5', size=3}},
'Number', 3, {data={is_float=true, base=10, val=2.5}, str='2.5'})
simple_test({{data='2.5e5', size=4}},
'Number', 1, {data={is_float=false, base=10, val=2}, str='2'})
simple_test({{data='2.5e-50', size=6}},
'Number', 6, {data={is_float=true, base=10, val=2.5e-5}, str='2.5e-5'})
end)
itp('treats `is` as an identifier', function()
flags = tonumber(lib.kELFlagIsNotCmp)
stable_tests()
regular_eoc_tests()
regular_scope_tests()
regular_number_tests()
simple_test({'is'}, 'PlainIdentifier', 2, {data={scope=0, autoload=false}, str='is'})
simple_test({'isnot'}, 'PlainIdentifier', 5, {data={scope=0, autoload=false}, str='isnot'})
simple_test({'is?'}, 'PlainIdentifier', 2, {data={scope=0, autoload=false}, str='is'})
simple_test({'isnot?'}, 'PlainIdentifier', 5, {data={scope=0, autoload=false}, str='isnot'})
simple_test({'is#'}, 'PlainIdentifier', 3, {data={scope=0, autoload=true}, str='is#'})
simple_test({'isnot#'}, 'PlainIdentifier', 6, {data={scope=0, autoload=true}, str='isnot#'})
simple_test({'is#foo'}, 'PlainIdentifier', 6, {data={scope=0, autoload=true}, str='is#foo'})
simple_test({'isnot#foo'}, 'PlainIdentifier', 9, {data={scope=0, autoload=true}, str='isnot#foo'})
end)
itp('forbids EOC', function()
flags = tonumber(lib.kELFlagForbidEOC)
stable_tests()
regular_scope_tests()
regular_is_tests()
regular_number_tests()
singl_eltkn_test('Invalid', '|', {error='E15: Unexpected EOC character: %.*s'})
singl_eltkn_test('Invalid', '\0', {error='E15: Unexpected EOC character: %.*s'})
singl_eltkn_test('Invalid', '\n', {error='E15: Unexpected EOC character: %.*s'})
end)
end)

View File

@@ -0,0 +1,540 @@
local helpers = require('test.unit.helpers')(after_each)
local global_helpers = require('test.helpers')
local itp = helpers.gen_itp(it)
local viml_helpers = require('test.unit.viml.helpers')
local make_enum_conv_tab = helpers.make_enum_conv_tab
local child_call_once = helpers.child_call_once
local alloc_log_new = helpers.alloc_log_new
local kvi_destroy = helpers.kvi_destroy
local conv_enum = helpers.conv_enum
local debug_log = helpers.debug_log
local ptr2key = helpers.ptr2key
local cimport = helpers.cimport
local ffi = helpers.ffi
local neq = helpers.neq
local eq = helpers.eq
local conv_ccs = viml_helpers.conv_ccs
local new_pstate = viml_helpers.new_pstate
local conv_cmp_type = viml_helpers.conv_cmp_type
local pstate_set_str = viml_helpers.pstate_set_str
local conv_expr_asgn_type = viml_helpers.conv_expr_asgn_type
local mergedicts_copy = global_helpers.mergedicts_copy
local format_string = global_helpers.format_string
local format_luav = global_helpers.format_luav
local intchar2lua = global_helpers.intchar2lua
local dictdiff = global_helpers.dictdiff
local lib = cimport('./src/nvim/viml/parser/expressions.h',
'./src/nvim/syntax.h')
local alloc_log = alloc_log_new()
local predefined_hl_defs = {
-- From highlight_init_both
Conceal=true,
Cursor=true,
lCursor=true,
DiffText=true,
ErrorMsg=true,
IncSearch=true,
ModeMsg=true,
NonText=true,
PmenuSbar=true,
StatusLine=true,
StatusLineNC=true,
TabLineFill=true,
TabLineSel=true,
TermCursor=true,
VertSplit=true,
WildMenu=true,
EndOfBuffer=true,
QuickFixLine=true,
Substitute=true,
Whitespace=true,
-- From highlight_init_(dark|light)
ColorColumn=true,
CursorColumn=true,
CursorLine=true,
CursorLineNr=true,
DiffAdd=true,
DiffChange=true,
DiffDelete=true,
Directory=true,
FoldColumn=true,
Folded=true,
LineNr=true,
MatchParen=true,
MoreMsg=true,
Pmenu=true,
PmenuSel=true,
PmenuThumb=true,
Question=true,
Search=true,
SignColumn=true,
SpecialKey=true,
SpellBad=true,
SpellCap=true,
SpellLocal=true,
SpellRare=true,
TabLine=true,
Title=true,
Visual=true,
WarningMsg=true,
Normal=true,
-- From syncolor.vim, if &background
Comment=true,
Constant=true,
Special=true,
Identifier=true,
Statement=true,
PreProc=true,
Type=true,
Underlined=true,
Ignore=true,
-- From syncolor.vim, below if &background
Error=true,
Todo=true,
-- From syncolor.vim, links at the bottom
String=true,
Character=true,
Number=true,
Boolean=true,
Float=true,
Function=true,
Conditional=true,
Repeat=true,
Label=true,
Operator=true,
Keyword=true,
Exception=true,
Include=true,
Define=true,
Macro=true,
PreCondit=true,
StorageClass=true,
Structure=true,
Typedef=true,
Tag=true,
SpecialChar=true,
Delimiter=true,
SpecialComment=true,
Debug=true,
}
local nvim_hl_defs = {}
child_call_once(function()
local i = 0
while lib.highlight_init_cmdline[i] ~= nil do
local hl_args = lib.highlight_init_cmdline[i]
local s = ffi.string(hl_args)
local err, msg = pcall(function()
if s:sub(1, 13) == 'default link ' then
local new_grp, grp_link = s:match('^default link (%w+) (%w+)$')
neq(nil, new_grp)
-- Note: group to link to must be already defined at the time of
-- linking, otherwise it will be created as cleared. So existence
-- of the group is checked here and not in the next pass over
-- nvim_hl_defs.
eq(true, not not (nvim_hl_defs[grp_link]
or predefined_hl_defs[grp_link]))
eq(false, not not (nvim_hl_defs[new_grp]
or predefined_hl_defs[new_grp]))
nvim_hl_defs[new_grp] = {'link', grp_link}
else
local new_grp, grp_args = s:match('^(%w+) (.*)')
neq(nil, new_grp)
eq(false, not not (nvim_hl_defs[new_grp]
or predefined_hl_defs[new_grp]))
nvim_hl_defs[new_grp] = {'definition', grp_args}
end
end)
if not err then
msg = format_string(
'Error while processing string %s at position %u:\n%s', s, i, msg)
error(msg)
end
i = i + 1
end
for k, _ in ipairs(nvim_hl_defs) do
eq('Nvim', k:sub(1, 4))
-- NvimInvalid
-- 12345678901
local err, msg = pcall(function()
if k:sub(5, 11) == 'Invalid' then
neq(nil, nvim_hl_defs['Nvim' .. k:sub(12)])
else
neq(nil, nvim_hl_defs['NvimInvalid' .. k:sub(5)])
end
end)
if not err then
msg = format_string('Error while processing group %s:\n%s', k, msg)
error(msg)
end
end
end)
local function hls_to_hl_fs(hls)
local ret = {}
local next_col = 0
for i, v in ipairs(hls) do
local group, line, col, str = v:match('^Nvim([a-zA-Z]+):(%d+):(%d+):(.*)$')
col = tonumber(col)
line = tonumber(line)
assert(line == 0)
local col_shift = col - next_col
assert(col_shift >= 0)
next_col = col + #str
ret[i] = format_string('hl(%r, %r%s)',
group,
str,
(col_shift == 0
and ''
or (', %u'):format(col_shift)))
end
return ret
end
local function format_check(expr, format_check_data, opts)
-- That forces specific order.
local zflags = opts.flags[1]
local zdata = format_check_data[zflags]
local dig_len
if opts.funcname then
print(format_string('\n%s(%r, {', opts.funcname, expr))
dig_len = #opts.funcname + 2
else
print(format_string('\n_check_parsing(%r, %r, {', opts, expr))
dig_len = #('_check_parsing(, \'') + #(format_string('%r', opts))
end
local digits = ' --' .. (' '):rep(dig_len - #(' --'))
local digits2 = digits:sub(1, -10)
for i = 0, #expr - 1 do
if i % 10 == 0 then
digits2 = ('%s%10u'):format(digits2, i / 10)
end
digits = ('%s%u'):format(digits, i % 10)
end
print(digits)
if #expr > 10 then
print(digits2)
end
if zdata.ast.len then
print((' len = %u,'):format(zdata.ast.len))
end
print(' ast = ' .. format_luav(zdata.ast.ast, ' ') .. ',')
if zdata.ast.err then
print(' err = {')
print(' arg = ' .. format_luav(zdata.ast.err.arg) .. ',')
print(' msg = ' .. format_luav(zdata.ast.err.msg) .. ',')
print(' },')
end
print('}, {')
for _, v in ipairs(zdata.hl_fs) do
print(' ' .. v .. ',')
end
local diffs = {}
local diffs_num = 0
for flags, v in pairs(format_check_data) do
if flags ~= zflags then
diffs[flags] = dictdiff(zdata, v)
if diffs[flags] then
if flags == 3 + zflags then
if (dictdiff(format_check_data[1 + zflags],
format_check_data[3 + zflags]) == nil
or dictdiff(format_check_data[2 + zflags],
format_check_data[3 + zflags]) == nil)
then
diffs[flags] = nil
else
diffs_num = diffs_num + 1
end
else
diffs_num = diffs_num + 1
end
end
end
end
if diffs_num ~= 0 then
print('}, {')
local flags = 1
while diffs_num ~= 0 do
if diffs[flags] then
diffs_num = diffs_num - 1
local diff = diffs[flags]
print((' [%u] = {'):format(flags))
if diff.ast then
print(' ast = ' .. format_luav(diff.ast, ' ') .. ',')
end
if diff.hl_fs then
print(' hl_fs = ' .. format_luav(diff.hl_fs, ' ', {
literal_strings=true
}) .. ',')
end
print(' },')
end
flags = flags + 1
end
end
print('})')
end
local east_node_type_tab
make_enum_conv_tab(lib, {
'kExprNodeMissing',
'kExprNodeOpMissing',
'kExprNodeTernary',
'kExprNodeTernaryValue',
'kExprNodeRegister',
'kExprNodeSubscript',
'kExprNodeListLiteral',
'kExprNodeUnaryPlus',
'kExprNodeBinaryPlus',
'kExprNodeNested',
'kExprNodeCall',
'kExprNodePlainIdentifier',
'kExprNodePlainKey',
'kExprNodeComplexIdentifier',
'kExprNodeUnknownFigure',
'kExprNodeLambda',
'kExprNodeDictLiteral',
'kExprNodeCurlyBracesIdentifier',
'kExprNodeComma',
'kExprNodeColon',
'kExprNodeArrow',
'kExprNodeComparison',
'kExprNodeConcat',
'kExprNodeConcatOrSubscript',
'kExprNodeInteger',
'kExprNodeFloat',
'kExprNodeSingleQuotedString',
'kExprNodeDoubleQuotedString',
'kExprNodeOr',
'kExprNodeAnd',
'kExprNodeUnaryMinus',
'kExprNodeBinaryMinus',
'kExprNodeNot',
'kExprNodeMultiplication',
'kExprNodeDivision',
'kExprNodeMod',
'kExprNodeOption',
'kExprNodeEnvironment',
'kExprNodeAssignment',
}, 'kExprNode', function(ret) east_node_type_tab = ret end)
local function conv_east_node_type(typ)
return conv_enum(east_node_type_tab, typ)
end
local eastnodelist2lua
local function eastnode2lua(pstate, eastnode, checked_nodes)
local key = ptr2key(eastnode)
if checked_nodes[key] then
checked_nodes[key].duplicate_key = key
return { duplicate = key }
end
local typ = conv_east_node_type(eastnode.type)
local ret = {}
checked_nodes[key] = ret
ret.children = eastnodelist2lua(pstate, eastnode.children, checked_nodes)
local str = pstate_set_str(pstate, eastnode.start, eastnode.len)
local ret_str
if str.error then
ret_str = 'error:' .. str.error
else
ret_str = ('%u:%u:%s'):format(str.start.line, str.start.col, str.str)
end
if typ == 'Register' then
typ = typ .. ('(name=%s)'):format(
tostring(intchar2lua(eastnode.data.reg.name)))
elseif typ == 'PlainIdentifier' then
typ = typ .. ('(scope=%s,ident=%s)'):format(
tostring(intchar2lua(eastnode.data.var.scope)),
ffi.string(eastnode.data.var.ident, eastnode.data.var.ident_len))
elseif typ == 'PlainKey' then
typ = typ .. ('(key=%s)'):format(
ffi.string(eastnode.data.var.ident, eastnode.data.var.ident_len))
elseif (typ == 'UnknownFigure' or typ == 'DictLiteral'
or typ == 'CurlyBracesIdentifier' or typ == 'Lambda') then
typ = typ .. ('(%s)'):format(
(eastnode.data.fig.type_guesses.allow_lambda and '\\' or '-')
.. (eastnode.data.fig.type_guesses.allow_dict and 'd' or '-')
.. (eastnode.data.fig.type_guesses.allow_ident and 'i' or '-'))
elseif typ == 'Comparison' then
typ = typ .. ('(type=%s,inv=%u,ccs=%s)'):format(
conv_cmp_type(eastnode.data.cmp.type), eastnode.data.cmp.inv and 1 or 0,
conv_ccs(eastnode.data.cmp.ccs))
elseif typ == 'Integer' then
typ = typ .. ('(val=%u)'):format(tonumber(eastnode.data.num.value))
elseif typ == 'Float' then
typ = typ .. format_string('(val=%e)', tonumber(eastnode.data.flt.value))
elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then
if eastnode.data.str.value == nil then
typ = typ .. '(val=NULL)'
else
local s = ffi.string(eastnode.data.str.value, eastnode.data.str.size)
typ = format_string('%s(val=%q)', typ, s)
end
elseif typ == 'Option' then
typ = ('%s(scope=%s,ident=%s)'):format(
typ,
tostring(intchar2lua(eastnode.data.opt.scope)),
ffi.string(eastnode.data.opt.ident, eastnode.data.opt.ident_len))
elseif typ == 'Environment' then
typ = ('%s(ident=%s)'):format(
typ,
ffi.string(eastnode.data.env.ident, eastnode.data.env.ident_len))
elseif typ == 'Assignment' then
typ = ('%s(%s)'):format(typ, conv_expr_asgn_type(eastnode.data.ass.type))
end
ret_str = typ .. ':' .. ret_str
local can_simplify = not ret.children
if can_simplify then
ret = ret_str
else
ret[1] = ret_str
end
return ret
end
eastnodelist2lua = function(pstate, eastnode, checked_nodes)
local ret = {}
while eastnode ~= nil do
ret[#ret + 1] = eastnode2lua(pstate, eastnode, checked_nodes)
eastnode = eastnode.next
end
if #ret == 0 then
ret = nil
end
return ret
end
local function east2lua(str, pstate, east)
local checked_nodes = {}
local len = tonumber(pstate.pos.col)
if pstate.pos.line == 1 then
len = tonumber(pstate.reader.lines.items[0].size)
end
if type(str) == 'string' and len == #str then
len = nil
end
return {
err = east.err.msg ~= nil and {
msg = ffi.string(east.err.msg),
arg = ffi.string(east.err.arg, east.err.arg_len),
} or nil,
len = len,
ast = eastnodelist2lua(pstate, east.root, checked_nodes),
}
end
local function phl2lua(pstate)
local ret = {}
for i = 0, (tonumber(pstate.colors.size) - 1) do
local chunk = pstate.colors.items[i]
local chunk_tbl = pstate_set_str(
pstate, chunk.start, chunk.end_col - chunk.start.col, {
group = ffi.string(chunk.group),
})
ret[i + 1] = ('%s:%u:%u:%s'):format(
chunk_tbl.group,
chunk_tbl.start.line,
chunk_tbl.start.col,
chunk_tbl.str)
end
return ret
end
child_call_once(function()
assert:set_parameter('TableFormatLevel', 1000000)
end)
describe('Expressions parser', function()
local function _check_parsing(opts, str, exp_ast, exp_highlighting_fs,
nz_flags_exps)
local zflags = opts.flags[1]
nz_flags_exps = nz_flags_exps or {}
local format_check_data = {}
for _, flags in ipairs(opts.flags) do
debug_log(('Running test case (%s, %u)'):format(str, flags))
local err, msg = pcall(function()
if os.getenv('NVIM_TEST_PARSER_SPEC_PRINT_TEST_CASE') == '1' then
print(str, flags)
end
alloc_log:check({})
local pstate = new_pstate({str})
local east = lib.viml_pexpr_parse(pstate, flags)
local ast = east2lua(str, pstate, east)
local hls = phl2lua(pstate)
if exp_ast == nil then
format_check_data[flags] = {ast=ast, hl_fs=hls_to_hl_fs(hls)}
else
local exps = {
ast = exp_ast,
hl_fs = exp_highlighting_fs,
}
local add_exps = nz_flags_exps[flags]
if not add_exps and flags == 3 + zflags then
add_exps = nz_flags_exps[1 + zflags] or nz_flags_exps[2 + zflags]
end
if add_exps then
if add_exps.ast then
exps.ast = mergedicts_copy(exps.ast, add_exps.ast)
end
if add_exps.hl_fs then
exps.hl_fs = mergedicts_copy(exps.hl_fs, add_exps.hl_fs)
end
end
eq(exps.ast, ast)
if exp_highlighting_fs then
local exp_highlighting = {}
local next_col = 0
for i, h in ipairs(exps.hl_fs) do
exp_highlighting[i], next_col = h(next_col)
end
eq(exp_highlighting, hls)
end
end
lib.viml_pexpr_free_ast(east)
kvi_destroy(pstate.colors)
alloc_log:clear_tmp_allocs(true)
alloc_log:check({})
end)
if not err then
msg = format_string('Error while processing test (%r, %u):\n%s',
str, flags, msg)
error(msg)
end
end
if exp_ast == nil then
format_check(str, format_check_data, opts)
end
end
local function hl(group, str, shift)
return function(next_col)
if nvim_hl_defs['Nvim' .. group] == nil then
error(('Unknown group: Nvim%s'):format(group))
end
local col = next_col + (shift or 0)
return (('%s:%u:%u:%s'):format(
'Nvim' .. group,
0,
col,
str)), (col + #str)
end
end
local function fmtn(typ, args, rest)
return ('%s(%s)%s'):format(typ, args, rest)
end
require('test.unit.viml.expressions.parser_tests')(
itp, _check_parsing, hl, fmtn)
end)

File diff suppressed because it is too large Load Diff

130
test/unit/viml/helpers.lua Normal file
View File

@@ -0,0 +1,130 @@
local helpers = require('test.unit.helpers')(nil)
local ffi = helpers.ffi
local cimport = helpers.cimport
local kvi_new = helpers.kvi_new
local kvi_init = helpers.kvi_init
local conv_enum = helpers.conv_enum
local make_enum_conv_tab = helpers.make_enum_conv_tab
local lib = cimport('./src/nvim/viml/parser/expressions.h')
local function new_pstate(strings)
local strings_idx = 0
local function get_line(_, ret_pline)
strings_idx = strings_idx + 1
local str = strings[strings_idx]
local data, size
if type(str) == 'string' then
data = str
size = #str
elseif type(str) == 'nil' then
data = nil
size = 0
elseif type(str) == 'table' then
data = str.data
size = str.size
elseif type(str) == 'function' then
data, size = str()
size = size or 0
end
ret_pline.data = data
ret_pline.size = size
ret_pline.allocated = false
end
local state = {
reader = {
get_line = get_line,
cookie = nil,
conv = {
vc_type = 0,
vc_factor = 1,
vc_fail = false,
},
},
pos = { line = 0, col = 0 },
colors = kvi_new('ParserHighlight'),
can_continuate = false,
}
local ret = ffi.new('ParserState', state)
kvi_init(ret.reader.lines)
kvi_init(ret.stack)
return ret
end
local function pline2lua(pline)
return ffi.string(pline.data, pline.size)
end
local function pstate_str(pstate, start, len)
local str = nil
local err = nil
if start.line < pstate.reader.lines.size then
local pstr = pline2lua(pstate.reader.lines.items[start.line])
if start.col >= #pstr then
err = 'start.col >= #pstr'
else
str = pstr:sub(tonumber(start.col) + 1, tonumber(start.col + len))
end
else
err = 'start.line >= pstate.reader.lines.size'
end
return str, err
end
local function pstate_set_str(pstate, start, len, ret)
ret = ret or {}
ret.start = {
line = tonumber(start.line),
col = tonumber(start.col)
}
ret.len = tonumber(len)
ret.str, ret.error = pstate_str(pstate, start, len)
return ret
end
local eltkn_cmp_type_tab
make_enum_conv_tab(lib, {
'kExprCmpEqual',
'kExprCmpMatches',
'kExprCmpGreater',
'kExprCmpGreaterOrEqual',
'kExprCmpIdentical',
}, 'kExprCmp', function(ret) eltkn_cmp_type_tab = ret end)
local function conv_cmp_type(typ)
return conv_enum(eltkn_cmp_type_tab, typ)
end
local ccs_tab
make_enum_conv_tab(lib, {
'kCCStrategyUseOption',
'kCCStrategyMatchCase',
'kCCStrategyIgnoreCase',
}, 'kCCStrategy', function(ret) ccs_tab = ret end)
local function conv_ccs(ccs)
return conv_enum(ccs_tab, ccs)
end
local expr_asgn_type_tab
make_enum_conv_tab(lib, {
'kExprAsgnPlain',
'kExprAsgnAdd',
'kExprAsgnSubtract',
'kExprAsgnConcat',
}, 'kExprAsgn', function(ret) expr_asgn_type_tab = ret end)
local function conv_expr_asgn_type(expr_asgn_type)
return conv_enum(expr_asgn_type_tab, expr_asgn_type)
end
return {
conv_ccs = conv_ccs,
pline2lua = pline2lua,
pstate_str = pstate_str,
new_pstate = new_pstate,
conv_cmp_type = conv_cmp_type,
pstate_set_str = pstate_set_str,
conv_expr_asgn_type = conv_expr_asgn_type,
}