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 }