neovim

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

cdoc_parser.lua (6128B)


      1 local cdoc_grammar = require('gen.cdoc_grammar')
      2 local c_grammar = require('gen.c_grammar')
      3 local api_type = require('gen.api_types')
      4 
      5 --- @class nvim.cdoc.parser.param
      6 --- @field name string
      7 --- @field type string
      8 --- @field desc string
      9 
     10 --- @class nvim.cdoc.parser.return
     11 --- @field name string
     12 --- @field type string
     13 --- @field desc string
     14 
     15 --- @class nvim.cdoc.parser.note
     16 --- @field desc string
     17 
     18 --- @class nvim.cdoc.parser.brief
     19 --- @field kind 'brief'
     20 --- @field desc string
     21 
     22 --- @class nvim.cdoc.parser.fun
     23 --- @field name string
     24 --- @field params nvim.cdoc.parser.param[]
     25 --- @field returns nvim.cdoc.parser.return[]
     26 --- @field desc string
     27 --- @field deprecated? true
     28 --- @field since? string
     29 --- @field attrs? string[]
     30 --- @field nodoc? true
     31 --- @field notes? nvim.cdoc.parser.note[]
     32 --- @field see? nvim.cdoc.parser.note[]
     33 
     34 --- @class nvim.cdoc.parser.State
     35 --- @field doc_lines? string[]
     36 --- @field cur_obj? nvim.cdoc.parser.obj
     37 --- @field last_doc_item? nvim.cdoc.parser.param|nvim.cdoc.parser.return|nvim.cdoc.parser.note
     38 --- @field last_doc_item_indent? integer
     39 
     40 --- @alias nvim.cdoc.parser.obj
     41 --- | nvim.cdoc.parser.fun
     42 --- | nvim.cdoc.parser.brief
     43 
     44 --- If we collected any `---` lines. Add them to the existing (or new) object
     45 --- Used for function/class descriptions and multiline param descriptions.
     46 --- @param state nvim.cdoc.parser.State
     47 local function add_doc_lines_to_obj(state)
     48  if state.doc_lines then
     49    state.cur_obj = state.cur_obj or {}
     50    local cur_obj = assert(state.cur_obj)
     51    local txt = table.concat(state.doc_lines, '\n')
     52    if cur_obj.desc then
     53      cur_obj.desc = cur_obj.desc .. '\n' .. txt
     54    else
     55      cur_obj.desc = txt
     56    end
     57    state.doc_lines = nil
     58  end
     59 end
     60 
     61 --- @param line string
     62 --- @param state nvim.cdoc.parser.State
     63 local function process_doc_line(line, state)
     64  line = line:gsub('^%s+@', '@')
     65 
     66  local parsed = cdoc_grammar:match(line)
     67 
     68  if not parsed then
     69    if line:match('^ ') then
     70      line = line:sub(2)
     71    end
     72 
     73    if state.last_doc_item then
     74      if not state.last_doc_item_indent then
     75        state.last_doc_item_indent = #line:match('^%s*') + 1
     76      end
     77      state.last_doc_item.desc = (state.last_doc_item.desc or '')
     78        .. '\n'
     79        .. line:sub(state.last_doc_item_indent or 1)
     80    else
     81      state.doc_lines = state.doc_lines or {}
     82      table.insert(state.doc_lines, line)
     83    end
     84    return
     85  end
     86 
     87  state.last_doc_item_indent = nil
     88  state.last_doc_item = nil
     89 
     90  local kind = parsed.kind
     91 
     92  state.cur_obj = state.cur_obj or {}
     93  local cur_obj = assert(state.cur_obj)
     94 
     95  if kind == 'brief' then
     96    state.cur_obj = {
     97      kind = 'brief',
     98      desc = parsed.desc,
     99    }
    100  elseif kind == 'param' then
    101    state.last_doc_item_indent = nil
    102    cur_obj.params = cur_obj.params or {}
    103    state.last_doc_item = {
    104      name = parsed.name,
    105      desc = parsed.desc,
    106    }
    107    table.insert(cur_obj.params, state.last_doc_item)
    108  elseif kind == 'return' then
    109    cur_obj.returns = { {
    110      desc = parsed.desc,
    111    } }
    112    state.last_doc_item_indent = nil
    113    state.last_doc_item = cur_obj.returns[1]
    114  elseif kind == 'deprecated' then
    115    cur_obj.deprecated = true
    116  elseif kind == 'nodoc' then
    117    cur_obj.nodoc = true
    118  elseif kind == 'since' then
    119    cur_obj.since = parsed.desc
    120  elseif kind == 'see' then
    121    cur_obj.see = cur_obj.see or {}
    122    table.insert(cur_obj.see, { desc = parsed.desc })
    123  elseif kind == 'note' then
    124    state.last_doc_item_indent = nil
    125    state.last_doc_item = {
    126      desc = parsed.desc,
    127    }
    128    cur_obj.notes = cur_obj.notes or {}
    129    table.insert(cur_obj.notes, state.last_doc_item)
    130  else
    131    error('Unhandled' .. vim.inspect(parsed))
    132  end
    133 end
    134 
    135 --- @param item table
    136 --- @param state nvim.cdoc.parser.State
    137 local function process_proto(item, state)
    138  state.cur_obj = state.cur_obj or {}
    139  local cur_obj = assert(state.cur_obj)
    140  cur_obj.name = item.name
    141  cur_obj.params = cur_obj.params or {}
    142 
    143  for _, p in ipairs(item.parameters) do
    144    local event_type = 'vim.api.keyset.events|vim.api.keyset.events[]'
    145    local event = (item.name == 'nvim_create_autocmd' or item.name == 'nvim_exec_autocmds')
    146      and p[2] == 'event'
    147    local param = { name = p[2], type = event and event_type or api_type(p[1]) }
    148    local added = false
    149 
    150    for _, cp in ipairs(cur_obj.params) do
    151      if cp.name == param.name then
    152        cp.type = param.type
    153        added = true
    154        break
    155      end
    156    end
    157 
    158    if not added then
    159      table.insert(cur_obj.params, param)
    160    end
    161  end
    162 
    163  cur_obj.returns = cur_obj.returns or { {} }
    164  cur_obj.returns[1].type = api_type(item.return_type)
    165 
    166  for _, a in ipairs({
    167    'fast',
    168    'remote_only',
    169    'lua_only',
    170    'textlock',
    171    'textlock_allow_cmdwin',
    172  }) do
    173    if item[a] then
    174      cur_obj.attrs = cur_obj.attrs or {}
    175      table.insert(cur_obj.attrs, a)
    176    end
    177  end
    178 
    179  cur_obj.since = item.since
    180  cur_obj.deprecated_since = item.deprecated_since
    181 
    182  -- Remove some arguments
    183  for i = #cur_obj.params, 1, -1 do
    184    local p = cur_obj.params[i]
    185    if p.name == 'channel_id' or vim.tbl_contains({ 'lstate', 'arena', 'error' }, p.type) then
    186      table.remove(cur_obj.params, i)
    187    end
    188  end
    189 end
    190 
    191 local M = {}
    192 
    193 --- @param filename string
    194 --- @return {} classes
    195 --- @return nvim.cdoc.parser.fun[] funs
    196 --- @return string[] briefs
    197 function M.parse(filename)
    198  local funs = {} --- @type nvim.cdoc.parser.fun[]
    199  local briefs = {} --- @type string[]
    200  local state = {} --- @type nvim.cdoc.parser.State
    201 
    202  local txt = assert(io.open(filename, 'r')):read('*all')
    203 
    204  local parsed = c_grammar.grammar:match(txt)
    205  for _, item in ipairs(parsed) do
    206    if item.comment then
    207      process_doc_line(item.comment, state)
    208    else
    209      add_doc_lines_to_obj(state)
    210      if item[1] == 'proto' then
    211        process_proto(item, state)
    212        table.insert(funs, state.cur_obj)
    213      end
    214      local cur_obj = state.cur_obj
    215      if cur_obj and not item.static then
    216        if cur_obj.kind == 'brief' then
    217          table.insert(briefs, cur_obj.desc)
    218        end
    219      end
    220      state = {}
    221    end
    222  end
    223 
    224  return {}, funs, briefs
    225 end
    226 
    227 -- M.parse('src/nvim/api/vim.c')
    228 
    229 return M