parser_spec.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. local helpers = require('test.unit.helpers')(after_each)
  2. local itp = helpers.gen_itp(it)
  3. local viml_helpers = require('test.unit.viml.helpers')
  4. local make_enum_conv_tab = helpers.make_enum_conv_tab
  5. local child_call_once = helpers.child_call_once
  6. local alloc_log_new = helpers.alloc_log_new
  7. local kvi_destroy = helpers.kvi_destroy
  8. local conv_enum = helpers.conv_enum
  9. local debug_log = helpers.debug_log
  10. local ptr2key = helpers.ptr2key
  11. local cimport = helpers.cimport
  12. local ffi = helpers.ffi
  13. local neq = helpers.neq
  14. local eq = helpers.eq
  15. local mergedicts_copy = helpers.mergedicts_copy
  16. local format_string = helpers.format_string
  17. local format_luav = helpers.format_luav
  18. local intchar2lua = helpers.intchar2lua
  19. local dictdiff = helpers.dictdiff
  20. local conv_ccs = viml_helpers.conv_ccs
  21. local new_pstate = viml_helpers.new_pstate
  22. local conv_cmp_type = viml_helpers.conv_cmp_type
  23. local pstate_set_str = viml_helpers.pstate_set_str
  24. local conv_expr_asgn_type = viml_helpers.conv_expr_asgn_type
  25. local lib = cimport('./src/nvim/viml/parser/expressions.h',
  26. './src/nvim/syntax.h')
  27. local alloc_log = alloc_log_new()
  28. local predefined_hl_defs = {
  29. -- From highlight_init_both
  30. Conceal=true,
  31. Cursor=true,
  32. lCursor=true,
  33. DiffText=true,
  34. ErrorMsg=true,
  35. IncSearch=true,
  36. ModeMsg=true,
  37. NonText=true,
  38. PmenuSbar=true,
  39. StatusLine=true,
  40. StatusLineNC=true,
  41. TabLineFill=true,
  42. TabLineSel=true,
  43. TermCursor=true,
  44. VertSplit=true,
  45. WildMenu=true,
  46. WinSeparator=true,
  47. EndOfBuffer=true,
  48. QuickFixLine=true,
  49. Substitute=true,
  50. Whitespace=true,
  51. Error=true,
  52. Todo=true,
  53. String=true,
  54. Character=true,
  55. Number=true,
  56. Boolean=true,
  57. Float=true,
  58. Function=true,
  59. Conditional=true,
  60. Repeat=true,
  61. Label=true,
  62. Operator=true,
  63. Keyword=true,
  64. Exception=true,
  65. Include=true,
  66. Define=true,
  67. Macro=true,
  68. PreCondit=true,
  69. StorageClass=true,
  70. Structure=true,
  71. Typedef=true,
  72. Tag=true,
  73. SpecialChar=true,
  74. Delimiter=true,
  75. SpecialComment=true,
  76. Debug=true,
  77. -- From highlight_init_(dark|light)
  78. ColorColumn=true,
  79. CursorColumn=true,
  80. CursorLine=true,
  81. CursorLineNr=true,
  82. DiffAdd=true,
  83. DiffChange=true,
  84. DiffDelete=true,
  85. Directory=true,
  86. FoldColumn=true,
  87. Folded=true,
  88. LineNr=true,
  89. MatchParen=true,
  90. MoreMsg=true,
  91. Pmenu=true,
  92. PmenuSel=true,
  93. PmenuThumb=true,
  94. Question=true,
  95. Search=true,
  96. SignColumn=true,
  97. SpecialKey=true,
  98. SpellBad=true,
  99. SpellCap=true,
  100. SpellLocal=true,
  101. SpellRare=true,
  102. TabLine=true,
  103. Title=true,
  104. Visual=true,
  105. WarningMsg=true,
  106. Normal=true,
  107. Comment=true,
  108. Constant=true,
  109. Special=true,
  110. Identifier=true,
  111. Statement=true,
  112. PreProc=true,
  113. Type=true,
  114. Underlined=true,
  115. Ignore=true,
  116. }
  117. local nvim_hl_defs = {}
  118. child_call_once(function()
  119. local i = 0
  120. while lib.highlight_init_cmdline[i] ~= nil do
  121. local hl_args = lib.highlight_init_cmdline[i]
  122. local s = ffi.string(hl_args)
  123. local err, msg = pcall(function()
  124. if s:sub(1, 13) == 'default link ' then
  125. local new_grp, grp_link = s:match('^default link (%w+) (%w+)$')
  126. neq(nil, new_grp)
  127. -- Note: group to link to must be already defined at the time of
  128. -- linking, otherwise it will be created as cleared. So existence
  129. -- of the group is checked here and not in the next pass over
  130. -- nvim_hl_defs.
  131. eq(true, not not (nvim_hl_defs[grp_link]
  132. or predefined_hl_defs[grp_link]))
  133. eq(false, not not (nvim_hl_defs[new_grp]
  134. or predefined_hl_defs[new_grp]))
  135. nvim_hl_defs[new_grp] = {'link', grp_link}
  136. else
  137. local new_grp, grp_args = s:match('^(%w+) (.*)')
  138. neq(nil, new_grp)
  139. eq(false, not not (nvim_hl_defs[new_grp]
  140. or predefined_hl_defs[new_grp]))
  141. nvim_hl_defs[new_grp] = {'definition', grp_args}
  142. end
  143. end)
  144. if not err then
  145. msg = format_string(
  146. 'Error while processing string %s at position %u:\n%s', s, i, msg)
  147. error(msg)
  148. end
  149. i = i + 1
  150. end
  151. for k, _ in ipairs(nvim_hl_defs) do
  152. eq('Nvim', k:sub(1, 4))
  153. -- NvimInvalid
  154. -- 12345678901
  155. local err, msg = pcall(function()
  156. if k:sub(5, 11) == 'Invalid' then
  157. neq(nil, nvim_hl_defs['Nvim' .. k:sub(12)])
  158. else
  159. neq(nil, nvim_hl_defs['NvimInvalid' .. k:sub(5)])
  160. end
  161. end)
  162. if not err then
  163. msg = format_string('Error while processing group %s:\n%s', k, msg)
  164. error(msg)
  165. end
  166. end
  167. end)
  168. local function hls_to_hl_fs(hls)
  169. local ret = {}
  170. local next_col = 0
  171. for i, v in ipairs(hls) do
  172. local group, line, col, str = v:match('^Nvim([a-zA-Z]+):(%d+):(%d+):(.*)$')
  173. col = tonumber(col)
  174. line = tonumber(line)
  175. assert(line == 0)
  176. local col_shift = col - next_col
  177. assert(col_shift >= 0)
  178. next_col = col + #str
  179. ret[i] = format_string('hl(%r, %r%s)',
  180. group,
  181. str,
  182. (col_shift == 0
  183. and ''
  184. or (', %u'):format(col_shift)))
  185. end
  186. return ret
  187. end
  188. local function format_check(expr, format_check_data, opts)
  189. -- That forces specific order.
  190. local zflags = opts.flags[1]
  191. local zdata = format_check_data[zflags]
  192. local dig_len
  193. if opts.funcname then
  194. print(format_string('\n%s(%r, {', opts.funcname, expr))
  195. dig_len = #opts.funcname + 2
  196. else
  197. print(format_string('\n_check_parsing(%r, %r, {', opts, expr))
  198. dig_len = #('_check_parsing(, \'') + #(format_string('%r', opts))
  199. end
  200. local digits = ' --' .. (' '):rep(dig_len - #(' --'))
  201. local digits2 = digits:sub(1, -10)
  202. for i = 0, #expr - 1 do
  203. if i % 10 == 0 then
  204. digits2 = ('%s%10u'):format(digits2, i / 10)
  205. end
  206. digits = ('%s%u'):format(digits, i % 10)
  207. end
  208. print(digits)
  209. if #expr > 10 then
  210. print(digits2)
  211. end
  212. if zdata.ast.len then
  213. print((' len = %u,'):format(zdata.ast.len))
  214. end
  215. print(' ast = ' .. format_luav(zdata.ast.ast, ' ') .. ',')
  216. if zdata.ast.err then
  217. print(' err = {')
  218. print(' arg = ' .. format_luav(zdata.ast.err.arg) .. ',')
  219. print(' msg = ' .. format_luav(zdata.ast.err.msg) .. ',')
  220. print(' },')
  221. end
  222. print('}, {')
  223. for _, v in ipairs(zdata.hl_fs) do
  224. print(' ' .. v .. ',')
  225. end
  226. local diffs = {}
  227. local diffs_num = 0
  228. for flags, v in pairs(format_check_data) do
  229. if flags ~= zflags then
  230. diffs[flags] = dictdiff(zdata, v)
  231. if diffs[flags] then
  232. if flags == 3 + zflags then
  233. if (dictdiff(format_check_data[1 + zflags],
  234. format_check_data[3 + zflags]) == nil
  235. or dictdiff(format_check_data[2 + zflags],
  236. format_check_data[3 + zflags]) == nil)
  237. then
  238. diffs[flags] = nil
  239. else
  240. diffs_num = diffs_num + 1
  241. end
  242. else
  243. diffs_num = diffs_num + 1
  244. end
  245. end
  246. end
  247. end
  248. if diffs_num ~= 0 then
  249. print('}, {')
  250. local flags = 1
  251. while diffs_num ~= 0 do
  252. if diffs[flags] then
  253. diffs_num = diffs_num - 1
  254. local diff = diffs[flags]
  255. print((' [%u] = {'):format(flags))
  256. if diff.ast then
  257. print(' ast = ' .. format_luav(diff.ast, ' ') .. ',')
  258. end
  259. if diff.hl_fs then
  260. print(' hl_fs = ' .. format_luav(diff.hl_fs, ' ', {
  261. literal_strings=true
  262. }) .. ',')
  263. end
  264. print(' },')
  265. end
  266. flags = flags + 1
  267. end
  268. end
  269. print('})')
  270. end
  271. local east_node_type_tab
  272. make_enum_conv_tab(lib, {
  273. 'kExprNodeMissing',
  274. 'kExprNodeOpMissing',
  275. 'kExprNodeTernary',
  276. 'kExprNodeTernaryValue',
  277. 'kExprNodeRegister',
  278. 'kExprNodeSubscript',
  279. 'kExprNodeListLiteral',
  280. 'kExprNodeUnaryPlus',
  281. 'kExprNodeBinaryPlus',
  282. 'kExprNodeNested',
  283. 'kExprNodeCall',
  284. 'kExprNodePlainIdentifier',
  285. 'kExprNodePlainKey',
  286. 'kExprNodeComplexIdentifier',
  287. 'kExprNodeUnknownFigure',
  288. 'kExprNodeLambda',
  289. 'kExprNodeDictLiteral',
  290. 'kExprNodeCurlyBracesIdentifier',
  291. 'kExprNodeComma',
  292. 'kExprNodeColon',
  293. 'kExprNodeArrow',
  294. 'kExprNodeComparison',
  295. 'kExprNodeConcat',
  296. 'kExprNodeConcatOrSubscript',
  297. 'kExprNodeInteger',
  298. 'kExprNodeFloat',
  299. 'kExprNodeSingleQuotedString',
  300. 'kExprNodeDoubleQuotedString',
  301. 'kExprNodeOr',
  302. 'kExprNodeAnd',
  303. 'kExprNodeUnaryMinus',
  304. 'kExprNodeBinaryMinus',
  305. 'kExprNodeNot',
  306. 'kExprNodeMultiplication',
  307. 'kExprNodeDivision',
  308. 'kExprNodeMod',
  309. 'kExprNodeOption',
  310. 'kExprNodeEnvironment',
  311. 'kExprNodeAssignment',
  312. }, 'kExprNode', function(ret) east_node_type_tab = ret end)
  313. local function conv_east_node_type(typ)
  314. return conv_enum(east_node_type_tab, typ)
  315. end
  316. local eastnodelist2lua
  317. local function eastnode2lua(pstate, eastnode, checked_nodes)
  318. local key = ptr2key(eastnode)
  319. if checked_nodes[key] then
  320. checked_nodes[key].duplicate_key = key
  321. return { duplicate = key }
  322. end
  323. local typ = conv_east_node_type(eastnode.type)
  324. local ret = {}
  325. checked_nodes[key] = ret
  326. ret.children = eastnodelist2lua(pstate, eastnode.children, checked_nodes)
  327. local str = pstate_set_str(pstate, eastnode.start, eastnode.len)
  328. local ret_str
  329. if str.error then
  330. ret_str = 'error:' .. str.error
  331. else
  332. ret_str = ('%u:%u:%s'):format(str.start.line, str.start.col, str.str)
  333. end
  334. if typ == 'Register' then
  335. typ = typ .. ('(name=%s)'):format(
  336. tostring(intchar2lua(eastnode.data.reg.name)))
  337. elseif typ == 'PlainIdentifier' then
  338. typ = typ .. ('(scope=%s,ident=%s)'):format(
  339. tostring(intchar2lua(eastnode.data.var.scope)),
  340. ffi.string(eastnode.data.var.ident, eastnode.data.var.ident_len))
  341. elseif typ == 'PlainKey' then
  342. typ = typ .. ('(key=%s)'):format(
  343. ffi.string(eastnode.data.var.ident, eastnode.data.var.ident_len))
  344. elseif (typ == 'UnknownFigure' or typ == 'DictLiteral'
  345. or typ == 'CurlyBracesIdentifier' or typ == 'Lambda') then
  346. typ = typ .. ('(%s)'):format(
  347. (eastnode.data.fig.type_guesses.allow_lambda and '\\' or '-')
  348. .. (eastnode.data.fig.type_guesses.allow_dict and 'd' or '-')
  349. .. (eastnode.data.fig.type_guesses.allow_ident and 'i' or '-'))
  350. elseif typ == 'Comparison' then
  351. typ = typ .. ('(type=%s,inv=%u,ccs=%s)'):format(
  352. conv_cmp_type(eastnode.data.cmp.type), eastnode.data.cmp.inv and 1 or 0,
  353. conv_ccs(eastnode.data.cmp.ccs))
  354. elseif typ == 'Integer' then
  355. typ = typ .. ('(val=%u)'):format(tonumber(eastnode.data.num.value))
  356. elseif typ == 'Float' then
  357. typ = typ .. format_string('(val=%e)', tonumber(eastnode.data.flt.value))
  358. elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then
  359. if eastnode.data.str.value == nil then
  360. typ = typ .. '(val=NULL)'
  361. else
  362. local s = ffi.string(eastnode.data.str.value, eastnode.data.str.size)
  363. typ = format_string('%s(val=%q)', typ, s)
  364. end
  365. elseif typ == 'Option' then
  366. typ = ('%s(scope=%s,ident=%s)'):format(
  367. typ,
  368. tostring(intchar2lua(eastnode.data.opt.scope)),
  369. ffi.string(eastnode.data.opt.ident, eastnode.data.opt.ident_len))
  370. elseif typ == 'Environment' then
  371. typ = ('%s(ident=%s)'):format(
  372. typ,
  373. ffi.string(eastnode.data.env.ident, eastnode.data.env.ident_len))
  374. elseif typ == 'Assignment' then
  375. typ = ('%s(%s)'):format(typ, conv_expr_asgn_type(eastnode.data.ass.type))
  376. end
  377. ret_str = typ .. ':' .. ret_str
  378. local can_simplify = not ret.children
  379. if can_simplify then
  380. ret = ret_str
  381. else
  382. ret[1] = ret_str
  383. end
  384. return ret
  385. end
  386. eastnodelist2lua = function(pstate, eastnode, checked_nodes)
  387. local ret = {}
  388. while eastnode ~= nil do
  389. ret[#ret + 1] = eastnode2lua(pstate, eastnode, checked_nodes)
  390. eastnode = eastnode.next
  391. end
  392. if #ret == 0 then
  393. ret = nil
  394. end
  395. return ret
  396. end
  397. local function east2lua(str, pstate, east)
  398. local checked_nodes = {}
  399. local len = tonumber(pstate.pos.col)
  400. if pstate.pos.line == 1 then
  401. len = tonumber(pstate.reader.lines.items[0].size)
  402. end
  403. if type(str) == 'string' and len == #str then
  404. len = nil
  405. end
  406. return {
  407. err = east.err.msg ~= nil and {
  408. msg = ffi.string(east.err.msg),
  409. arg = ffi.string(east.err.arg, east.err.arg_len),
  410. } or nil,
  411. len = len,
  412. ast = eastnodelist2lua(pstate, east.root, checked_nodes),
  413. }
  414. end
  415. local function phl2lua(pstate)
  416. local ret = {}
  417. for i = 0, (tonumber(pstate.colors.size) - 1) do
  418. local chunk = pstate.colors.items[i]
  419. local chunk_tbl = pstate_set_str(
  420. pstate, chunk.start, chunk.end_col - chunk.start.col, {
  421. group = ffi.string(chunk.group),
  422. })
  423. ret[i + 1] = ('%s:%u:%u:%s'):format(
  424. chunk_tbl.group,
  425. chunk_tbl.start.line,
  426. chunk_tbl.start.col,
  427. chunk_tbl.str)
  428. end
  429. return ret
  430. end
  431. describe('Expressions parser', function()
  432. local function _check_parsing(opts, str, exp_ast, exp_highlighting_fs,
  433. nz_flags_exps)
  434. local zflags = opts.flags[1]
  435. nz_flags_exps = nz_flags_exps or {}
  436. local format_check_data = {}
  437. for _, flags in ipairs(opts.flags) do
  438. debug_log(('Running test case (%s, %u)'):format(str, flags))
  439. local err, msg = pcall(function()
  440. if os.getenv('NVIM_TEST_PARSER_SPEC_PRINT_TEST_CASE') == '1' then
  441. print(str, flags)
  442. end
  443. alloc_log:check({})
  444. local pstate = new_pstate({str})
  445. local east = lib.viml_pexpr_parse(pstate, flags)
  446. local ast = east2lua(str, pstate, east)
  447. local hls = phl2lua(pstate)
  448. if exp_ast == nil then
  449. format_check_data[flags] = {ast=ast, hl_fs=hls_to_hl_fs(hls)}
  450. else
  451. local exps = {
  452. ast = exp_ast,
  453. hl_fs = exp_highlighting_fs,
  454. }
  455. local add_exps = nz_flags_exps[flags]
  456. if not add_exps and flags == 3 + zflags then
  457. add_exps = nz_flags_exps[1 + zflags] or nz_flags_exps[2 + zflags]
  458. end
  459. if add_exps then
  460. if add_exps.ast then
  461. exps.ast = mergedicts_copy(exps.ast, add_exps.ast)
  462. end
  463. if add_exps.hl_fs then
  464. exps.hl_fs = mergedicts_copy(exps.hl_fs, add_exps.hl_fs)
  465. end
  466. end
  467. eq(exps.ast, ast)
  468. if exp_highlighting_fs then
  469. local exp_highlighting = {}
  470. local next_col = 0
  471. for i, h in ipairs(exps.hl_fs) do
  472. exp_highlighting[i], next_col = h(next_col)
  473. end
  474. eq(exp_highlighting, hls)
  475. end
  476. end
  477. lib.viml_pexpr_free_ast(east)
  478. kvi_destroy(pstate.colors)
  479. alloc_log:clear_tmp_allocs(true)
  480. alloc_log:check({})
  481. end)
  482. if not err then
  483. msg = format_string('Error while processing test (%r, %u):\n%s',
  484. str, flags, msg)
  485. error(msg)
  486. end
  487. end
  488. if exp_ast == nil then
  489. format_check(str, format_check_data, opts)
  490. end
  491. end
  492. local function hl(group, str, shift)
  493. return function(next_col)
  494. if nvim_hl_defs['Nvim' .. group] == nil then
  495. error(('Unknown group: Nvim%s'):format(group))
  496. end
  497. local col = next_col + (shift or 0)
  498. return (('%s:%u:%u:%s'):format(
  499. 'Nvim' .. group,
  500. 0,
  501. col,
  502. str)), (col + #str)
  503. end
  504. end
  505. local function fmtn(typ, args, rest)
  506. return ('%s(%s)%s'):format(typ, args, rest)
  507. end
  508. require('test.unit.viml.expressions.parser_tests')(
  509. itp, _check_parsing, hl, fmtn)
  510. end)