neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

testutil.lua (15870B)


      1 local t = require('test.unit.testutil')
      2 
      3 local ptr2key = t.ptr2key
      4 local cimport = t.cimport
      5 local to_cstr = t.to_cstr
      6 local ffi = t.ffi
      7 local eq = t.eq
      8 
      9 local eval = cimport(
     10  './src/nvim/eval.h',
     11  './src/nvim/eval/typval.h',
     12  './src/nvim/hashtab.h',
     13  './src/nvim/memory.h'
     14 )
     15 
     16 local null_string = { [true] = 'NULL string' }
     17 local null_list = { [true] = 'NULL list' }
     18 local null_dict = { [true] = 'NULL dict' }
     19 local type_key = { [true] = 'type key' }
     20 local locks_key = { [true] = 'locks key' }
     21 local list_type = { [true] = 'list type' }
     22 local dict_type = { [true] = 'dict type' }
     23 local func_type = { [true] = 'func type' }
     24 local int_type = { [true] = 'int type' }
     25 local flt_type = { [true] = 'flt type' }
     26 
     27 local nil_value = { [true] = 'nil' }
     28 
     29 local lua2typvalt
     30 
     31 local function tv_list_item_alloc()
     32  return ffi.cast('listitem_T*', eval.xmalloc(ffi.sizeof('listitem_T')))
     33 end
     34 
     35 local function tv_list_item_free(li)
     36  eval.tv_clear(li.li_tv)
     37  eval.xfree(li)
     38 end
     39 
     40 local function li_alloc(nogc)
     41  local gcfunc = tv_list_item_free
     42  if nogc then
     43    gcfunc = nil
     44  end
     45  local li = ffi.gc(tv_list_item_alloc(), gcfunc)
     46  li.li_next = nil
     47  li.li_prev = nil
     48  li.li_tv = { v_type = eval.VAR_UNKNOWN, v_lock = eval.VAR_UNLOCKED }
     49  return li
     50 end
     51 
     52 local function populate_list(l, lua_l, processed)
     53  processed = processed or {}
     54  eq(0, l.lv_refcount)
     55  l.lv_refcount = 1
     56  processed[lua_l] = l
     57  for i = 1, #lua_l do
     58    local item_tv = ffi.gc(lua2typvalt(lua_l[i], processed), nil)
     59    local item_li = tv_list_item_alloc()
     60    item_li.li_tv = item_tv
     61    eval.tv_list_append(l, item_li)
     62  end
     63  return l
     64 end
     65 
     66 local function populate_dict(d, lua_d, processed)
     67  processed = processed or {}
     68  eq(0, d.dv_refcount)
     69  d.dv_refcount = 1
     70  processed[lua_d] = d
     71  for k, v in pairs(lua_d) do
     72    if type(k) == 'string' then
     73      local di = eval.tv_dict_item_alloc(to_cstr(k))
     74      local val_tv = ffi.gc(lua2typvalt(v, processed), nil)
     75      eval.tv_copy(val_tv, di.di_tv)
     76      eval.tv_clear(val_tv)
     77      eval.tv_dict_add(d, di)
     78    end
     79  end
     80  return d
     81 end
     82 
     83 local function populate_partial(pt, lua_pt, processed)
     84  processed = processed or {}
     85  eq(0, pt.pt_refcount)
     86  processed[lua_pt] = pt
     87  local argv = nil
     88  if lua_pt.args and #lua_pt.args > 0 then
     89    argv = ffi.gc(ffi.cast('typval_T*', eval.xmalloc(ffi.sizeof('typval_T') * #lua_pt.args)), nil)
     90    for i, arg in ipairs(lua_pt.args) do
     91      local arg_tv = ffi.gc(lua2typvalt(arg, processed), nil)
     92      argv[i - 1] = arg_tv
     93    end
     94  end
     95  local dict = nil
     96  if lua_pt.dict then
     97    local dict_tv = ffi.gc(lua2typvalt(lua_pt.dict, processed), nil)
     98    assert(dict_tv.v_type == eval.VAR_DICT)
     99    dict = dict_tv.vval.v_dict
    100  end
    101  pt.pt_refcount = 1
    102  pt.pt_name = eval.xmemdupz(to_cstr(lua_pt.value), #lua_pt.value)
    103  pt.pt_auto = not not lua_pt.auto
    104  pt.pt_argc = lua_pt.args and #lua_pt.args or 0
    105  pt.pt_argv = argv
    106  pt.pt_dict = dict
    107  return pt
    108 end
    109 
    110 local lst2tbl
    111 local dct2tbl
    112 
    113 local typvalt2lua
    114 
    115 local function partial2lua(pt, processed)
    116  processed = processed or {}
    117  local value, auto, dict, argv = nil, nil, nil, nil
    118  if pt ~= nil then
    119    value = ffi.string(pt.pt_name)
    120    auto = pt.pt_auto and true or nil
    121    argv = {}
    122    for i = 1, pt.pt_argc do
    123      argv[i] = typvalt2lua(pt.pt_argv[i - 1], processed)
    124    end
    125    if pt.pt_dict ~= nil then
    126      dict = dct2tbl(pt.pt_dict)
    127    end
    128  end
    129  return {
    130    [type_key] = func_type,
    131    value = value,
    132    auto = auto,
    133    args = argv,
    134    dict = dict,
    135  }
    136 end
    137 
    138 local typvalt2lua_tab = nil
    139 
    140 local function typvalt2lua_tab_init()
    141  if typvalt2lua_tab then
    142    return
    143  end
    144  typvalt2lua_tab = {
    145    [tonumber(eval.VAR_BOOL)] = function(q)
    146      return ({
    147        [tonumber(eval.kBoolVarFalse)] = false,
    148        [tonumber(eval.kBoolVarTrue)] = true,
    149      })[tonumber(q.vval.v_bool)]
    150    end,
    151    [tonumber(eval.VAR_SPECIAL)] = function(q)
    152      return ({
    153        [tonumber(eval.kSpecialVarNull)] = nil_value,
    154      })[tonumber(q.vval.v_special)]
    155    end,
    156    [tonumber(eval.VAR_NUMBER)] = function(q)
    157      return { [type_key] = int_type, value = tonumber(q.vval.v_number) }
    158    end,
    159    [tonumber(eval.VAR_FLOAT)] = function(q)
    160      return tonumber(q.vval.v_float)
    161    end,
    162    [tonumber(eval.VAR_STRING)] = function(q)
    163      local str = q.vval.v_string
    164      if str == nil then
    165        return null_string
    166      else
    167        return ffi.string(str)
    168      end
    169    end,
    170    [tonumber(eval.VAR_LIST)] = function(q, processed)
    171      return lst2tbl(q.vval.v_list, processed)
    172    end,
    173    [tonumber(eval.VAR_DICT)] = function(q, processed)
    174      return dct2tbl(q.vval.v_dict, processed)
    175    end,
    176    [tonumber(eval.VAR_FUNC)] = function(q, processed)
    177      return { [type_key] = func_type, value = typvalt2lua_tab[eval.VAR_STRING](q, processed or {}) }
    178    end,
    179    [tonumber(eval.VAR_PARTIAL)] = function(q, processed)
    180      local p_key = ptr2key(q)
    181      if processed[p_key] then
    182        return processed[p_key]
    183      end
    184      return partial2lua(q.vval.v_partial, processed)
    185    end,
    186  }
    187 end
    188 
    189 typvalt2lua = function(q, processed)
    190  typvalt2lua_tab_init()
    191  return (
    192    (typvalt2lua_tab[tonumber(q.v_type)] or function(t_inner)
    193      assert(false, 'Converting ' .. tonumber(t_inner.v_type) .. ' was not implemented yet')
    194    end)(q, processed or {})
    195  )
    196 end
    197 
    198 local function list_iter(l)
    199  local init_s = {
    200    idx = 0,
    201    li = l.lv_first,
    202  }
    203  local function f(s, _)
    204    -- (listitem_T *) NULL is equal to nil, but yet it is not false.
    205    if s.li == nil then
    206      return nil
    207    end
    208    local ret_li = s.li
    209    s.li = s.li.li_next
    210    s.idx = s.idx + 1
    211    return s.idx, ret_li
    212  end
    213  return f, init_s, nil
    214 end
    215 
    216 local function list_items(l)
    217  local ret = {}
    218  for i, li in list_iter(l) do
    219    ret[i] = li
    220  end
    221  return ret
    222 end
    223 
    224 lst2tbl = function(l, processed)
    225  if l == nil then
    226    return null_list
    227  end
    228  processed = processed or {}
    229  local p_key = ptr2key(l)
    230  if processed[p_key] then
    231    return processed[p_key]
    232  end
    233  local ret = { [type_key] = list_type }
    234  processed[p_key] = ret
    235  for i, li in list_iter(l) do
    236    ret[i] = typvalt2lua(li.li_tv, processed)
    237  end
    238  if ret[1] then
    239    ret[type_key] = nil
    240  end
    241  return ret
    242 end
    243 
    244 local hi_key_removed = nil
    245 
    246 local function dict_iter(d, return_hi)
    247  hi_key_removed = hi_key_removed or eval._hash_key_removed()
    248  local init_s = {
    249    todo = d.dv_hashtab.ht_used,
    250    hi = d.dv_hashtab.ht_array,
    251  }
    252  local function f(s, _)
    253    if s.todo == 0 then
    254      return nil
    255    end
    256    while s.todo > 0 do
    257      if s.hi.hi_key ~= nil and s.hi.hi_key ~= hi_key_removed then
    258        local key = ffi.string(s.hi.hi_key)
    259        local ret
    260        if return_hi then
    261          ret = s.hi
    262        else
    263          ret = ffi.cast('dictitem_T*', s.hi.hi_key - ffi.offsetof('dictitem_T', 'di_key'))
    264        end
    265        s.todo = s.todo - 1
    266        s.hi = s.hi + 1
    267        return key, ret
    268      end
    269      s.hi = s.hi + 1
    270    end
    271  end
    272  return f, init_s, nil
    273 end
    274 
    275 local function first_di(d)
    276  local f, init_s, v = dict_iter(d)
    277  return select(2, f(init_s, v))
    278 end
    279 
    280 local function dict_items(d)
    281  local ret = { [0] = 0 }
    282  for k, hi in dict_iter(d) do
    283    ret[k] = hi
    284    ret[0] = ret[0] + 1
    285    ret[ret[0]] = hi
    286  end
    287  return ret
    288 end
    289 
    290 dct2tbl = function(d, processed)
    291  if d == nil then
    292    return null_dict
    293  end
    294  processed = processed or {}
    295  local p_key = ptr2key(d)
    296  if processed[p_key] then
    297    return processed[p_key]
    298  end
    299  local ret = {}
    300  processed[p_key] = ret
    301  for k, di in dict_iter(d) do
    302    ret[k] = typvalt2lua(di.di_tv, processed)
    303  end
    304  return ret
    305 end
    306 
    307 local typvalt = function(typ, vval)
    308  if typ == nil then
    309    typ = eval.VAR_UNKNOWN
    310  elseif type(typ) == 'string' then
    311    typ = eval[typ]
    312  end
    313  return ffi.gc(ffi.new('typval_T', { v_type = typ, vval = vval }), eval.tv_clear)
    314 end
    315 
    316 local lua2typvalt_type_tab = {
    317  [int_type] = function(l, _)
    318    return typvalt(eval.VAR_NUMBER, { v_number = l.value })
    319  end,
    320  [flt_type] = function(l, processed)
    321    return lua2typvalt(l.value, processed)
    322  end,
    323  [list_type] = function(l, processed)
    324    if processed[l] then
    325      processed[l].lv_refcount = processed[l].lv_refcount + 1
    326      return typvalt(eval.VAR_LIST, { v_list = processed[l] })
    327    end
    328    local lst = populate_list(eval.tv_list_alloc(#l), l, processed)
    329    return typvalt(eval.VAR_LIST, { v_list = lst })
    330  end,
    331  [dict_type] = function(l, processed)
    332    if processed[l] then
    333      processed[l].dv_refcount = processed[l].dv_refcount + 1
    334      return typvalt(eval.VAR_DICT, { v_dict = processed[l] })
    335    end
    336    local dct = populate_dict(eval.tv_dict_alloc(), l, processed)
    337    return typvalt(eval.VAR_DICT, { v_dict = dct })
    338  end,
    339  [func_type] = function(l, processed)
    340    if processed[l] then
    341      processed[l].pt_refcount = processed[l].pt_refcount + 1
    342      return typvalt(eval.VAR_PARTIAL, { v_partial = processed[l] })
    343    end
    344    if l.args or l.dict then
    345      local pt = populate_partial(
    346        ffi.gc(ffi.cast('partial_T*', eval.xcalloc(1, ffi.sizeof('partial_T'))), nil),
    347        l,
    348        processed
    349      )
    350      return typvalt(eval.VAR_PARTIAL, { v_partial = pt })
    351    else
    352      return typvalt(eval.VAR_FUNC, {
    353        v_string = eval.xmemdupz(to_cstr(l.value), #l.value),
    354      })
    355    end
    356  end,
    357 }
    358 
    359 local special_vals = nil
    360 
    361 lua2typvalt = function(l, processed)
    362  if not special_vals then
    363    special_vals = {
    364      [null_string] = { 'VAR_STRING', { v_string = ffi.cast('char*', nil) } },
    365      [null_list] = { 'VAR_LIST', { v_list = ffi.cast('list_T*', nil) } },
    366      [null_dict] = { 'VAR_DICT', { v_dict = ffi.cast('dict_T*', nil) } },
    367      [nil_value] = { 'VAR_SPECIAL', { v_special = eval.kSpecialVarNull } },
    368      [true] = { 'VAR_BOOL', { v_bool = eval.kBoolVarTrue } },
    369      [false] = { 'VAR_BOOL', { v_bool = eval.kBoolVarFalse } },
    370    }
    371 
    372    for k, v in pairs(special_vals) do
    373      local tmp = function(typ, vval)
    374        special_vals[k] = function()
    375          return typvalt(eval[typ], vval)
    376        end
    377      end
    378      tmp(v[1], v[2])
    379    end
    380  end
    381  processed = processed or {}
    382  if l == nil or l == nil_value then
    383    return special_vals[nil_value]()
    384  elseif special_vals[l] then
    385    return special_vals[l]()
    386  elseif type(l) == 'table' then
    387    if l[type_key] then
    388      return lua2typvalt_type_tab[l[type_key]](l, processed)
    389    else
    390      if l[1] then
    391        return lua2typvalt_type_tab[list_type](l, processed)
    392      else
    393        return lua2typvalt_type_tab[dict_type](l, processed)
    394      end
    395    end
    396  elseif type(l) == 'number' then
    397    return typvalt(eval.VAR_FLOAT, { v_float = l })
    398  elseif type(l) == 'string' then
    399    return typvalt(eval.VAR_STRING, { v_string = eval.xmemdupz(to_cstr(l), #l) })
    400  elseif type(l) == 'cdata' then
    401    local tv = typvalt(eval.VAR_UNKNOWN)
    402    eval.tv_copy(l, tv)
    403    return tv
    404  end
    405 end
    406 
    407 local void_ptr = ffi.typeof('void *')
    408 local function void(ptr)
    409  return ffi.cast(void_ptr, ptr)
    410 end
    411 
    412 local function alloc_len(len, get_ptr)
    413  if type(len) == 'string' or type(len) == 'table' then
    414    return #len
    415  elseif len == nil then
    416    return tonumber(eval.strlen(get_ptr()))
    417  else
    418    return len
    419  end
    420 end
    421 
    422 local alloc_logging_t = {
    423  list = function(l)
    424    return { func = 'calloc', args = { 1, ffi.sizeof('list_T') }, ret = void(l) }
    425  end,
    426  li = function(li)
    427    return { func = 'malloc', args = { ffi.sizeof('listitem_T') }, ret = void(li) }
    428  end,
    429  dict = function(d)
    430    return { func = 'calloc', args = { 1, ffi.sizeof('dict_T') }, ret = void(d) }
    431  end,
    432  di = function(di, size)
    433    size = alloc_len(size, function()
    434      return di.di_key
    435    end)
    436    return {
    437      func = 'malloc',
    438      args = { math.max(ffi.sizeof('dictitem_T'), ffi.offsetof('dictitem_T', 'di_key') + size + 1) },
    439      ret = void(di),
    440    }
    441  end,
    442  str = function(s, size)
    443    size = alloc_len(size, function()
    444      return s
    445    end)
    446    return { func = 'malloc', args = { size + 1 }, ret = void(s) }
    447  end,
    448 
    449  dwatcher = function(w)
    450    return { func = 'malloc', args = { ffi.sizeof('DictWatcher') }, ret = void(w) }
    451  end,
    452 
    453  freed = function(p)
    454    return { func = 'free', args = { type(p) == 'table' and p or void(p) } }
    455  end,
    456 
    457  -- lua_…: allocated by this file, not by some Neovim function
    458  lua_pt = function(pt)
    459    return { func = 'calloc', args = { 1, ffi.sizeof('partial_T') }, ret = void(pt) }
    460  end,
    461  lua_tvs = function(argv, argc)
    462    argc = alloc_len(argc)
    463    return { func = 'malloc', args = { ffi.sizeof('typval_T') * argc }, ret = void(argv) }
    464  end,
    465 }
    466 
    467 local function int(n)
    468  return { [type_key] = int_type, value = n }
    469 end
    470 
    471 local function list(...)
    472  return populate_list(
    473    ffi.gc(eval.tv_list_alloc(select('#', ...)), eval.tv_list_unref),
    474    { ... },
    475    {}
    476  )
    477 end
    478 
    479 local function dict(d)
    480  return populate_dict(ffi.gc(eval.tv_dict_alloc(), eval.tv_dict_free), d or {}, {})
    481 end
    482 
    483 local callback2tbl_type_tab = nil
    484 
    485 local function init_callback2tbl_type_tab()
    486  if callback2tbl_type_tab then
    487    return
    488  end
    489  callback2tbl_type_tab = {
    490    [tonumber(eval.kCallbackNone)] = function(_)
    491      return { type = 'none' }
    492    end,
    493    [tonumber(eval.kCallbackFuncref)] = function(cb)
    494      return { type = 'fref', fref = ffi.string(cb.data.funcref) }
    495    end,
    496    [tonumber(eval.kCallbackPartial)] = function(cb)
    497      local lua_pt = partial2lua(cb.data.partial)
    498      return { type = 'pt', fref = ffi.string(lua_pt.value), pt = lua_pt }
    499    end,
    500  }
    501 end
    502 
    503 local function callback2tbl(cb)
    504  init_callback2tbl_type_tab()
    505  return callback2tbl_type_tab[tonumber(cb.type)](cb)
    506 end
    507 
    508 local function tbl2callback(tbl)
    509  local ret = nil
    510  if tbl.type == 'none' then
    511    ret = ffi.new('Callback[1]', { { type = eval.kCallbackNone } })
    512  elseif tbl.type == 'fref' then
    513    ret = ffi.new(
    514      'Callback[1]',
    515      { { type = eval.kCallbackFuncref, data = { funcref = eval.xstrdup(tbl.fref) } } }
    516    )
    517  elseif tbl.type == 'pt' then
    518    local pt = ffi.gc(ffi.cast('partial_T*', eval.xcalloc(1, ffi.sizeof('partial_T'))), nil)
    519    ret = ffi.new(
    520      'Callback[1]',
    521      { { type = eval.kCallbackPartial, data = { partial = populate_partial(pt, tbl.pt, {}) } } }
    522    )
    523  else
    524    assert(false)
    525  end
    526  return ffi.gc(ffi.cast('Callback*', ret), t.callback_free)
    527 end
    528 
    529 local function dict_watchers(d)
    530  local ret = {}
    531  local h = d.watchers
    532  local q = h.next
    533  local qs = {}
    534  local key_patterns = {}
    535  while q ~= h do
    536    local qitem =
    537      ffi.cast('DictWatcher *', ffi.cast('char *', q) - ffi.offsetof('DictWatcher', 'node'))
    538    ret[#ret + 1] = {
    539      cb = callback2tbl(qitem.callback),
    540      pat = ffi.string(qitem.key_pattern, qitem.key_pattern_len),
    541      busy = qitem.busy,
    542    }
    543    qs[#qs + 1] = qitem
    544    key_patterns[#key_patterns + 1] = { qitem.key_pattern, qitem.key_pattern_len }
    545    q = q.next
    546  end
    547  return ret, qs, key_patterns
    548 end
    549 
    550 local function eval0(expr)
    551  local tv = ffi.gc(ffi.new('typval_T', { v_type = eval.VAR_UNKNOWN }), eval.tv_clear)
    552  local evalarg = ffi.new('evalarg_T', { eval_flags = eval.EVAL_EVALUATE })
    553  if eval.eval0(to_cstr(expr), tv, nil, evalarg) == 0 then
    554    return nil
    555  else
    556    return tv
    557  end
    558 end
    559 
    560 return {
    561  int = int,
    562 
    563  null_string = null_string,
    564  null_list = null_list,
    565  null_dict = null_dict,
    566  list_type = list_type,
    567  dict_type = dict_type,
    568  func_type = func_type,
    569  int_type = int_type,
    570  flt_type = flt_type,
    571 
    572  nil_value = nil_value,
    573 
    574  type_key = type_key,
    575  locks_key = locks_key,
    576 
    577  list = list,
    578  dict = dict,
    579  lst2tbl = lst2tbl,
    580  dct2tbl = dct2tbl,
    581 
    582  lua2typvalt = lua2typvalt,
    583  typvalt2lua = typvalt2lua,
    584 
    585  typvalt = typvalt,
    586 
    587  li_alloc = li_alloc,
    588  tv_list_item_free = tv_list_item_free,
    589 
    590  dict_iter = dict_iter,
    591  list_iter = list_iter,
    592  first_di = first_di,
    593 
    594  alloc_logging_t = alloc_logging_t,
    595 
    596  list_items = list_items,
    597  dict_items = dict_items,
    598 
    599  dict_watchers = dict_watchers,
    600  tbl2callback = tbl2callback,
    601  callback2tbl = callback2tbl,
    602 
    603  eval0 = eval0,
    604 
    605  empty_list = { [type_key] = list_type },
    606 }