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