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