gen_declarations.lua (5203B)
1 local grammar = require('gen.c_grammar').grammar 2 3 --- @param fname string 4 --- @return string? 5 local function read_file(fname) 6 local f = io.open(fname, 'r') 7 if not f then 8 return 9 end 10 local contents = f:read('*a') 11 f:close() 12 return contents 13 end 14 15 --- @param fname string 16 --- @param contents string[] 17 local function write_file(fname, contents) 18 local contents_s = table.concat(contents, '\n') .. '\n' 19 local fcontents = read_file(fname) 20 if fcontents == contents_s then 21 return 22 end 23 local f = assert(io.open(fname, 'w')) 24 f:write(contents_s) 25 f:close() 26 end 27 28 --- @param fname string 29 --- @param non_static_fname string 30 --- @return string? non_static 31 local function add_iwyu_non_static(fname, non_static_fname) 32 if fname:find('.*/src/nvim/.*%.c$') then 33 -- Add an IWYU pragma comment if the corresponding .h file exists. 34 local header_fname = fname:sub(1, -3) .. '.h' 35 local header_f = io.open(header_fname, 'r') 36 if header_f then 37 header_f:close() 38 return (header_fname:gsub('.*/src/nvim/', 'nvim/')) 39 end 40 elseif non_static_fname:find('/include/api/private/dispatch_wrappers%.h%.generated%.h$') then 41 return 'nvim/api/private/dispatch.h' 42 elseif non_static_fname:find('/include/ui_events_call%.h%.generated%.h$') then 43 return 'nvim/ui.h' 44 elseif non_static_fname:find('/include/ui_events_client%.h%.generated%.h$') then 45 return 'nvim/ui_client.h' 46 elseif non_static_fname:find('/include/ui_events_remote%.h%.generated%.h$') then 47 return 'nvim/api/ui.h' 48 end 49 end 50 51 --- @param d string 52 local function process_decl(d) 53 -- Comments are really handled by preprocessor, so the following is not 54 -- needed 55 d = d:gsub('/%*.-%*/', '') 56 d = d:gsub('//.-\n', '\n') 57 d = d:gsub('# .-\n', '') 58 d = d:gsub('\n', ' ') 59 d = d:gsub('%s+', ' ') 60 d = d:gsub(' ?%( ?', '(') 61 d = d:gsub(' ?, ?', ', ') 62 d = d:gsub(' ?(%*+) ?', ' %1') 63 d = d:gsub(' ?(FUNC_ATTR_)', ' %1') 64 d = d:gsub(' $', '') 65 d = d:gsub('^ ', '') 66 return d .. ';' 67 end 68 69 --- @param fname string 70 --- @param text string 71 --- @return string[] static 72 --- @return string[] non_static 73 --- @return boolean any_static 74 local function gen_declarations(text) 75 local non_static = {} --- @type string[] 76 local static = {} --- @type string[] 77 78 local any_static = false 79 for _, node in ipairs(grammar:match(text)) do 80 if node[1] == 'proto' then 81 local node_text = text:sub(node.pos, node.endpos - 1) 82 local declaration = process_decl(node_text) 83 84 if node.static then 85 if not any_static and declaration:find('FUNC_ATTR_') then 86 any_static = true 87 end 88 static[#static + 1] = declaration 89 else 90 non_static[#non_static + 1] = 'DLLEXPORT ' .. declaration 91 end 92 end 93 end 94 95 return static, non_static, any_static 96 end 97 98 local usage = [[ 99 Usage: 100 101 gen_declarations.lua definitions.c static.h non-static.h 102 103 Generates declarations for a C file definitions.c, putting declarations for 104 static functions into static.h and declarations for non-static functions into 105 non-static.h. Also generate an IWYU comment. 106 ]] 107 108 local function main() 109 local fname = arg[1] 110 local static_fname = arg[2] 111 local non_static_fname = arg[3] 112 local static_basename = arg[4] 113 114 if fname == '--help' or #arg < 4 then 115 print(usage) 116 os.exit() 117 end 118 119 local text = assert(read_file(fname)) 120 121 local static_decls, non_static_decls, any_static = gen_declarations(text) 122 123 local static = {} --- @type string[] 124 if fname:find('.*/src/nvim/.*%.h$') then 125 static[#static + 1] = ('// IWYU pragma: private, include "%s"'):format( 126 fname:gsub('.*/src/nvim/', 'nvim/') 127 ) 128 end 129 vim.list_extend(static, { 130 '#define DEFINE_FUNC_ATTRIBUTES', 131 '#include "nvim/func_attr.h"', 132 '#undef DEFINE_FUNC_ATTRIBUTES', 133 }) 134 vim.list_extend(static, static_decls) 135 vim.list_extend(static, { 136 '#define DEFINE_EMPTY_ATTRIBUTES', 137 '#include "nvim/func_attr.h" // IWYU pragma: export', 138 '', 139 }) 140 141 write_file(static_fname, static) 142 143 if any_static then 144 local orig_text = assert(read_file(fname)) 145 local pat = '\n#%s?include%s+"' .. static_basename .. '"\n' 146 local pat_comment = '\n#%s?include%s+"' .. static_basename .. '"%s*//' 147 if not orig_text:find(pat) and not orig_text:find(pat_comment) then 148 error(('fail: missing include for %s in %s'):format(static_basename, fname)) 149 end 150 end 151 152 if non_static_fname ~= 'SKIP' then 153 local non_static = {} --- @type string[] 154 local iwyu_non_static = add_iwyu_non_static(fname, non_static_fname) 155 if iwyu_non_static then 156 non_static[#non_static + 1] = ('// IWYU pragma: private, include "%s"'):format( 157 iwyu_non_static 158 ) 159 end 160 vim.list_extend(non_static, { 161 '#define DEFINE_FUNC_ATTRIBUTES', 162 '#include "nvim/func_attr.h"', 163 '#undef DEFINE_FUNC_ATTRIBUTES', 164 '#ifndef DLLEXPORT', 165 '# ifdef MSWIN', 166 '# define DLLEXPORT __declspec(dllexport)', 167 '# else', 168 '# define DLLEXPORT', 169 '# endif', 170 '#endif', 171 }) 172 vim.list_extend(non_static, non_static_decls) 173 non_static[#non_static + 1] = '#include "nvim/func_attr.h"' 174 write_file(non_static_fname, non_static) 175 end 176 end 177 178 return main()