diff options
Diffstat (limited to 'src/nvim/generators/gen_api_dispatch.lua')
-rw-r--r-- | src/nvim/generators/gen_api_dispatch.lua | 714 |
1 files changed, 471 insertions, 243 deletions
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 9720cca477..04b4363e42 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -1,16 +1,19 @@ local mpack = vim.mpack -local hashy = require'generators.hashy' +local hashy = require 'generators.hashy' -assert(#arg >= 5) +local pre_args = 7 +assert(#arg >= pre_args) -- output h file with generated dispatch functions (dispatch_wrappers.generated.h) local dispatch_outputf = arg[1] --- output h file with packed metadata (funcs_metadata.generated.h) -local funcs_metadata_outputf = arg[2] +-- output h file with packed metadata (api_metadata.generated.h) +local api_metadata_outputf = arg[2] -- output metadata mpack file, for use by other build scripts (api_metadata.mpack) local mpack_outputf = arg[3] local lua_c_bindings_outputf = arg[4] -- lua_api_c_bindings.generated.c local keysets_outputf = arg[5] -- keysets_defs.generated.h +local ui_metadata_inputf = arg[6] -- ui events metadata +local git_version_inputf = arg[7] -- git version header local functions = {} @@ -23,16 +26,18 @@ local function_names = {} local c_grammar = require('generators.c_grammar') -local function startswith(String,Start) - return string.sub(String,1,string.len(Start))==Start -end +local startswith = vim.startswith local function add_function(fn) - local public = startswith(fn.name, "nvim_") or fn.deprecated_since + local public = startswith(fn.name, 'nvim_') or fn.deprecated_since if public and not fn.noexport then functions[#functions + 1] = fn function_names[fn.name] = true - if #fn.parameters >= 2 and fn.parameters[2][1] == 'Array' and fn.parameters[2][2] == 'uidata' then + if + #fn.parameters >= 2 + and fn.parameters[2][1] == 'Array' + and fn.parameters[2][2] == 'uidata' + then -- function receives the "args" as a parameter fn.receives_array_args = true -- remove the args parameter @@ -51,15 +56,14 @@ local function add_function(fn) -- for specifying errors fn.parameters[#fn.parameters] = nil end - if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'arena' then - -- return value is allocated in an arena - fn.arena_return = true - fn.parameters[#fn.parameters] = nil - end if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'lstate' then fn.has_lua_imp = true fn.parameters[#fn.parameters] = nil end + if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'arena' then + fn.receives_arena = true + fn.parameters[#fn.parameters] = nil + end end end @@ -70,7 +74,7 @@ local function add_keyset(val) local types = {} local is_set_name = 'is_set__' .. val.keyset_name .. '_' local has_optional = false - for i,field in ipairs(val.fields) do + for i, field in ipairs(val.fields) do if field.type ~= 'Object' then types[field.name] = field.type end @@ -80,36 +84,46 @@ local function add_keyset(val) if i > 1 then error("'is_set__{type}_' must be first if present") elseif field.name ~= is_set_name then - error(val.keyset_name..": name of first key should be "..is_set_name) + error(val.keyset_name .. ': name of first key should be ' .. is_set_name) elseif field.type ~= 'OptionalKeys' then - error("'"..is_set_name.."' must have type 'OptionalKeys'") + error("'" .. is_set_name .. "' must have type 'OptionalKeys'") end has_optional = true end end - table.insert(keysets, {name=val.keyset_name, keys=keys, types=types, has_optional=has_optional}) + table.insert(keysets, { + name = val.keyset_name, + keys = keys, + types = types, + has_optional = has_optional, + }) end +local ui_options_text = nil + -- read each input file, parse and append to the api metadata -for i = 6, #arg do +for i = pre_args + 1, #arg do local full_path = arg[i] local parts = {} for part in string.gmatch(full_path, '[^/]+') do parts[#parts + 1] = part end - headers[#headers + 1] = parts[#parts - 1]..'/'..parts[#parts] + headers[#headers + 1] = parts[#parts - 1] .. '/' .. parts[#parts] - local input = io.open(full_path, 'rb') + local input = assert(io.open(full_path, 'rb')) - local tmp = c_grammar.grammar:match(input:read('*all')) + local text = input:read('*all') + local tmp = c_grammar.grammar:match(text) for j = 1, #tmp do local val = tmp[j] if val.keyset_name then add_keyset(val) - else + elseif val.name then add_function(val) end end + + ui_options_text = ui_options_text or string.match(text, 'ui_ext_names%[][^{]+{([^}]+)}') input:close() end @@ -123,14 +137,14 @@ end -- Export functions under older deprecated names. -- These will be removed eventually. -local deprecated_aliases = require("api.dispatch_deprecated") -for _,f in ipairs(shallowcopy(functions)) do +local deprecated_aliases = require('api.dispatch_deprecated') +for _, f in ipairs(shallowcopy(functions)) do local ismethod = false - if startswith(f.name, "nvim_") then - if startswith(f.name, "nvim__") or f.name == "nvim_error_event" then + if startswith(f.name, 'nvim_') then + if startswith(f.name, 'nvim__') or f.name == 'nvim_error_event' then f.since = -1 elseif f.since == nil then - print("Function "..f.name.." lacks since field.\n") + print('Function ' .. f.name .. ' lacks since field.\n') os.exit(1) end f.since = tonumber(f.since) @@ -138,16 +152,16 @@ for _,f in ipairs(shallowcopy(functions)) do f.deprecated_since = tonumber(f.deprecated_since) end - if startswith(f.name, "nvim_buf_") then + if startswith(f.name, 'nvim_buf_') then ismethod = true - elseif startswith(f.name, "nvim_win_") then + elseif startswith(f.name, 'nvim_win_') then ismethod = true - elseif startswith(f.name, "nvim_tabpage_") then + elseif startswith(f.name, 'nvim_tabpage_') then ismethod = true end f.remote = f.remote_only or not f.lua_only f.lua = f.lua_only or not f.remote_only - f.eval = (not f.lua_only) and (not f.remote_only) + f.eval = (not f.lua_only) and not f.remote_only else f.deprecated_since = tonumber(f.deprecated_since) assert(f.deprecated_since == 1) @@ -159,63 +173,127 @@ for _,f in ipairs(shallowcopy(functions)) do if newname ~= nil then if function_names[newname] then -- duplicate - print("Function "..f.name.." has deprecated alias\n" - ..newname.." which has a separate implementation.\n".. - "Please remove it from src/nvim/api/dispatch_deprecated.lua") + print( + 'Function ' + .. f.name + .. ' has deprecated alias\n' + .. newname + .. ' which has a separate implementation.\n' + .. 'Please remove it from src/nvim/api/dispatch_deprecated.lua' + ) os.exit(1) end local newf = shallowcopy(f) newf.name = newname - if newname == "ui_try_resize" then + if newname == 'ui_try_resize' then -- The return type was incorrectly set to Object in 0.1.5. -- Keep it that way for clients that rely on this. - newf.return_type = "Object" + newf.return_type = 'Object' end newf.impl_name = f.name newf.lua = false newf.eval = false newf.since = 0 newf.deprecated_since = 1 - functions[#functions+1] = newf + functions[#functions + 1] = newf end end -- don't expose internal attributes like "impl_name" in public metadata -local exported_attributes = {'name', 'return_type', 'method', - 'since', 'deprecated_since'} +local exported_attributes = { 'name', 'return_type', 'method', 'since', 'deprecated_since' } local exported_functions = {} -for _,f in ipairs(functions) do - if not (startswith(f.name, "nvim__") or f.name == "nvim_error_event" or f.name == "redraw") then +for _, f in ipairs(functions) do + if not (startswith(f.name, 'nvim__') or f.name == 'nvim_error_event' or f.name == 'redraw') then local f_exported = {} - for _,attr in ipairs(exported_attributes) do + for _, attr in ipairs(exported_attributes) do f_exported[attr] = f[attr] end f_exported.parameters = {} - for i,param in ipairs(f.parameters) do - if param[1] == "DictionaryOf(LuaRef)" then - param = {"Dictionary", param[2]} - elseif startswith(param[1], "Dict(") then - param = {"Dictionary", param[2]} + for i, param in ipairs(f.parameters) do + if param[1] == 'DictionaryOf(LuaRef)' then + param = { 'Dictionary', param[2] } + elseif startswith(param[1], 'Dict(') then + param = { 'Dictionary', param[2] } end f_exported.parameters[i] = param end - exported_functions[#exported_functions+1] = f_exported + if startswith(f.return_type, 'Dict(') then + f_exported.return_type = 'Dictionary' + end + exported_functions[#exported_functions + 1] = f_exported end end +local ui_options = { 'rgb' } +for x in string.gmatch(ui_options_text, '"([a-z][a-z_]+)"') do + table.insert(ui_options, x) +end + +local version = require 'nvim_version' +local git_version = io.open(git_version_inputf):read '*a' +local version_build = string.match(git_version, '#define NVIM_VERSION_BUILD "([^"]+)"') or vim.NIL -- serialize the API metadata using msgpack and embed into the resulting -- binary for easy querying by clients -local funcs_metadata_output = io.open(funcs_metadata_outputf, 'wb') -local packed = mpack.encode(exported_functions) -local dump_bin_array = require("generators.dump_bin_array") -dump_bin_array(funcs_metadata_output, 'funcs_metadata', packed) -funcs_metadata_output:close() +local api_metadata_output = assert(io.open(api_metadata_outputf, 'wb')) +local pieces = {} + +-- Naively using mpack.encode({foo=x, bar=y}) will make the build +-- "non-reproducible". Emit maps directly as FIXDICT(2) "foo" x "bar" y instead +local function fixdict(num) + if num > 15 then + error 'implement more dict codes' + end + table.insert(pieces, string.char(128 + num)) +end +local function put(item, item2) + table.insert(pieces, mpack.encode(item)) + if item2 ~= nil then + table.insert(pieces, mpack.encode(item2)) + end +end + +fixdict(6) + +put('version') +fixdict(1 + #version) +for _, item in ipairs(version) do + -- NB: all items are mandatory. But any error will be less confusing + -- with placholder vim.NIL (than invalid mpack data) + put(item[1], item[2] or vim.NIL) +end +put('build', version_build) + +put('functions', exported_functions) +put('ui_events') +table.insert(pieces, io.open(ui_metadata_inputf, 'rb'):read('*all')) +put('ui_options', ui_options) + +put('error_types') +fixdict(2) +put('Exception', { id = 0 }) +put('Validation', { id = 1 }) + +put('types') +local types = + { { 'Buffer', 'nvim_buf_' }, { 'Window', 'nvim_win_' }, { 'Tabpage', 'nvim_tabpage_' } } +fixdict(#types) +for i, item in ipairs(types) do + put(item[1]) + fixdict(2) + put('id', i - 1) + put('prefix', item[2]) +end + +local packed = table.concat(pieces) +local dump_bin_array = require('generators.dump_bin_array') +dump_bin_array(api_metadata_output, 'packed_api_metadata', packed) +api_metadata_output:close() -- start building the dispatch wrapper output -local output = io.open(dispatch_outputf, 'wb') +local output = assert(io.open(dispatch_outputf, 'wb')) -local keysets_defs = io.open(keysets_outputf, 'wb') +local keysets_defs = assert(io.open(keysets_outputf, 'wb')) -- =========================================================================== -- NEW API FILES MUST GO HERE. @@ -226,9 +304,9 @@ local keysets_defs = io.open(keysets_outputf, 'wb') output:write([[ #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" +#include "nvim/globals.h" #include "nvim/log.h" #include "nvim/map_defs.h" -#include "nvim/msgpack_rpc/helpers.h" #include "nvim/api/autocmd.h" #include "nvim/api/buffer.h" @@ -246,67 +324,84 @@ output:write([[ ]]) -for _,k in ipairs(keysets) do +keysets_defs:write('// IWYU pragma: private, include "nvim/api/private/dispatch.h"\n\n') + +for _, k in ipairs(keysets) do local c_name = {} - for i = 1,#k.keys do + for i = 1, #k.keys do -- some keys, like "register" are c keywords and get -- escaped with a trailing _ in the struct. - if vim.endswith(k.keys[i], "_") then + if vim.endswith(k.keys[i], '_') then local orig = k.keys[i] - k.keys[i] = string.sub(k.keys[i],1, #(k.keys[i]) - 1) + k.keys[i] = string.sub(k.keys[i], 1, #k.keys[i] - 1) c_name[k.keys[i]] = orig k.types[k.keys[i]] = k.types[orig] end end - local neworder, hashfun = hashy.hashy_hash(k.name, k.keys, function (idx) - return k.name.."_table["..idx.."].str" + local neworder, hashfun = hashy.hashy_hash(k.name, k.keys, function(idx) + return k.name .. '_table[' .. idx .. '].str' end) - keysets_defs:write("extern KeySetLink "..k.name.."_table[];\n") + keysets_defs:write('extern KeySetLink ' .. k.name .. '_table[' .. (1 + #neworder) .. '];\n') local function typename(type) - if type ~= nil then - return "kObjectType"..type + if type == 'HLGroupID' then + return 'kObjectTypeInteger' + elseif type ~= nil then + return 'kObjectType' .. type else - return "kObjectTypeNil" + return 'kObjectTypeNil' end end - output:write("KeySetLink "..k.name.."_table[] = {\n") + output:write('KeySetLink ' .. k.name .. '_table[] = {\n') for i, key in ipairs(neworder) do local ind = -1 if k.has_optional then ind = i - keysets_defs:write("#define KEYSET_OPTIDX_"..k.name.."__"..key.." "..ind.."\n") - end - output:write(' {"'..key..'", offsetof(KeyDict_'..k.name..", "..(c_name[key] or key).."), "..typename(k.types[key])..", "..ind.."},\n") + keysets_defs:write('#define KEYSET_OPTIDX_' .. k.name .. '__' .. key .. ' ' .. ind .. '\n') + end + output:write( + ' {"' + .. key + .. '", offsetof(KeyDict_' + .. k.name + .. ', ' + .. (c_name[key] or key) + .. '), ' + .. typename(k.types[key]) + .. ', ' + .. ind + .. ', ' + .. (k.types[key] == 'HLGroupID' and 'true' or 'false') + .. '},\n' + ) end - output:write(' {NULL, 0, kObjectTypeNil, -1},\n') - output:write("};\n\n") + output:write(' {NULL, 0, kObjectTypeNil, -1, false},\n') + output:write('};\n\n') output:write(hashfun) output:write([[ -KeySetLink *KeyDict_]]..k.name..[[_get_field(const char *str, size_t len) +KeySetLink *KeyDict_]] .. k.name .. [[_get_field(const char *str, size_t len) { - int hash = ]]..k.name..[[_hash(str, len); + int hash = ]] .. k.name .. [[_hash(str, len); if (hash == -1) { return NULL; } - return &]]..k.name..[[_table[hash]; + return &]] .. k.name .. [[_table[hash]; } ]]) - keysets_defs:write("#define api_free_keydict_"..k.name.."(x) api_free_keydict(x, "..k.name.."_table)\n") end local function real_type(type) local rv = type - local rmatch = string.match(type, "Dict%(([_%w]+)%)") + local rmatch = string.match(type, 'Dict%(([_%w]+)%)') if rmatch then - return "KeyDict_"..rmatch + return 'KeyDict_' .. rmatch elseif c_grammar.typed_container:match(rv) then if rv:match('Array') then rv = 'Array' @@ -333,24 +428,30 @@ for i = 1, #functions do if fn.impl_name == nil and fn.remote then local args = {} - output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Arena* arena, Error *error)') + output:write( + 'Object handle_' .. fn.name .. '(uint64_t channel_id, Array args, Arena* arena, Error *error)' + ) output:write('\n{') output:write('\n#ifdef NVIM_LOG_DEBUG') - output:write('\n DLOG("RPC: ch %" PRIu64 ": invoke '..fn.name..'", channel_id);') + output:write('\n DLOG("RPC: ch %" PRIu64 ": invoke ' .. fn.name .. '", channel_id);') output:write('\n#endif') output:write('\n Object ret = NIL;') -- Declare/initialize variables that will hold converted arguments for j = 1, #fn.parameters do local param = fn.parameters[j] local rt = real_type(param[1]) - local converted = 'arg_'..j - output:write('\n '..rt..' '..converted..';') + local converted = 'arg_' .. j + output:write('\n ' .. rt .. ' ' .. converted .. ';') end output:write('\n') if not fn.receives_array_args then - output:write('\n if (args.size != '..#fn.parameters..') {') - output:write('\n api_set_error(error, kErrorTypeException, \ - "Wrong number of arguments: expecting '..#fn.parameters..' but got %zu", args.size);') + output:write('\n if (args.size != ' .. #fn.parameters .. ') {') + output:write( + '\n api_set_error(error, kErrorTypeException, \ + "Wrong number of arguments: expecting ' + .. #fn.parameters + .. ' but got %zu", args.size);' + ) output:write('\n goto cleanup;') output:write('\n }\n') end @@ -359,55 +460,121 @@ for i = 1, #functions do for j = 1, #fn.parameters do local converted, param param = fn.parameters[j] - converted = 'arg_'..j + converted = 'arg_' .. j local rt = real_type(param[1]) if rt == 'Object' then - output:write('\n '..converted..' = args.items['..(j - 1)..'];\n') + output:write('\n ' .. converted .. ' = args.items[' .. (j - 1) .. '];\n') elseif rt:match('^KeyDict_') then converted = '&' .. converted - output:write('\n if (args.items['..(j - 1)..'].type == kObjectTypeDictionary) {') --luacheck: ignore 631 - output:write('\n memset('..converted..', 0, sizeof(*'..converted..'));') -- TODO: neeeee - output:write('\n if (!api_dict_to_keydict('..converted..', '..rt..'_get_field, args.items['..(j - 1)..'].data.dictionary, error)) {') + output:write('\n if (args.items[' .. (j - 1) .. '].type == kObjectTypeDictionary) {') --luacheck: ignore 631 + output:write('\n memset(' .. converted .. ', 0, sizeof(*' .. converted .. '));') -- TODO: neeeee + output:write( + '\n if (!api_dict_to_keydict(' + .. converted + .. ', ' + .. rt + .. '_get_field, args.items[' + .. (j - 1) + .. '].data.dictionary, error)) {' + ) output:write('\n goto cleanup;') output:write('\n }') - output:write('\n } else if (args.items['..(j - 1)..'].type == kObjectTypeArray && args.items['..(j - 1)..'].data.array.size == 0) {') --luacheck: ignore 631 - output:write('\n memset('..converted..', 0, sizeof(*'..converted..'));') + output:write( + '\n } else if (args.items[' + .. (j - 1) + .. '].type == kObjectTypeArray && args.items[' + .. (j - 1) + .. '].data.array.size == 0) {' + ) --luacheck: ignore 631 + output:write('\n memset(' .. converted .. ', 0, sizeof(*' .. converted .. '));') output:write('\n } else {') - output:write('\n api_set_error(error, kErrorTypeException, \ - "Wrong type for argument '..j..' when calling '..fn.name..', expecting '..param[1]..'");') + output:write( + '\n api_set_error(error, kErrorTypeException, \ + "Wrong type for argument ' + .. j + .. ' when calling ' + .. fn.name + .. ', expecting ' + .. param[1] + .. '");' + ) output:write('\n goto cleanup;') output:write('\n }\n') else if rt:match('^Buffer$') or rt:match('^Window$') or rt:match('^Tabpage$') then -- Buffer, Window, and Tabpage have a specific type, but are stored in integer - output:write('\n if (args.items['.. - (j - 1)..'].type == kObjectType'..rt..' && args.items['..(j - 1)..'].data.integer >= 0) {') - output:write('\n '..converted..' = (handle_T)args.items['..(j - 1)..'].data.integer;') + output:write( + '\n if (args.items[' + .. (j - 1) + .. '].type == kObjectType' + .. rt + .. ' && args.items[' + .. (j - 1) + .. '].data.integer >= 0) {' + ) + output:write( + '\n ' .. converted .. ' = (handle_T)args.items[' .. (j - 1) .. '].data.integer;' + ) else - output:write('\n if (args.items['..(j - 1)..'].type == kObjectType'..rt..') {') - output:write('\n '..converted..' = args.items['..(j - 1)..'].data.'..attr_name(rt)..';') + output:write('\n if (args.items[' .. (j - 1) .. '].type == kObjectType' .. rt .. ') {') + output:write( + '\n ' + .. converted + .. ' = args.items[' + .. (j - 1) + .. '].data.' + .. attr_name(rt) + .. ';' + ) end - if rt:match('^Buffer$') or rt:match('^Window$') or rt:match('^Tabpage$') or rt:match('^Boolean$') then + if + rt:match('^Buffer$') + or rt:match('^Window$') + or rt:match('^Tabpage$') + or rt:match('^Boolean$') + then -- accept nonnegative integers for Booleans, Buffers, Windows and Tabpages - output:write('\n } else if (args.items['.. - (j - 1)..'].type == kObjectTypeInteger && args.items['..(j - 1)..'].data.integer >= 0) {') - output:write('\n '..converted..' = (handle_T)args.items['..(j - 1)..'].data.integer;') + output:write( + '\n } else if (args.items[' + .. (j - 1) + .. '].type == kObjectTypeInteger && args.items[' + .. (j - 1) + .. '].data.integer >= 0) {' + ) + output:write( + '\n ' .. converted .. ' = (handle_T)args.items[' .. (j - 1) .. '].data.integer;' + ) end if rt:match('^Float$') then -- accept integers for Floats - output:write('\n } else if (args.items['.. - (j - 1)..'].type == kObjectTypeInteger) {') - output:write('\n '..converted..' = (Float)args.items['..(j - 1)..'].data.integer;') + output:write('\n } else if (args.items[' .. (j - 1) .. '].type == kObjectTypeInteger) {') + output:write( + '\n ' .. converted .. ' = (Float)args.items[' .. (j - 1) .. '].data.integer;' + ) end -- accept empty lua tables as empty dictionaries if rt:match('^Dictionary') then - output:write('\n } else if (args.items['..(j - 1)..'].type == kObjectTypeArray && args.items['..(j - 1)..'].data.array.size == 0) {') --luacheck: ignore 631 - output:write('\n '..converted..' = (Dictionary)ARRAY_DICT_INIT;') + output:write( + '\n } else if (args.items[' + .. (j - 1) + .. '].type == kObjectTypeArray && args.items[' + .. (j - 1) + .. '].data.array.size == 0) {' + ) --luacheck: ignore 631 + output:write('\n ' .. converted .. ' = (Dictionary)ARRAY_DICT_INIT;') end output:write('\n } else {') - output:write('\n api_set_error(error, kErrorTypeException, \ - "Wrong type for argument '..j..' when calling '..fn.name..', expecting '..param[1]..'");') + output:write( + '\n api_set_error(error, kErrorTypeException, \ + "Wrong type for argument ' + .. j + .. ' when calling ' + .. fn.name + .. ', expecting ' + .. param[1] + .. '");' + ) output:write('\n goto cleanup;') output:write('\n }\n') end @@ -427,127 +594,127 @@ for i = 1, #functions do end -- function call - local call_args = table.concat(args, ', ') output:write('\n ') if fn.return_type ~= 'void' then -- has a return value, prefix the call with a declaration - output:write(fn.return_type..' rv = ') + output:write(fn.return_type .. ' rv = ') end -- write the function name and the opening parenthesis - output:write(fn.name..'(') + output:write(fn.name .. '(') + local call_args = {} if fn.receives_channel_id then - -- if the function receives the channel id, pass it as first argument - if #args > 0 or fn.can_fail then - output:write('channel_id, ') - if fn.receives_array_args then - -- if the function receives the array args, pass it the second argument - output:write('args, ') - end - output:write(call_args) - else - output:write('channel_id') - if fn.receives_array_args then - output:write(', args') - end - end - else - if fn.receives_array_args then - if #args > 0 or fn.call_fail then - output:write('args, '..call_args) - else - output:write('args') - end - else - output:write(call_args) - end + table.insert(call_args, 'channel_id') + end + + if fn.receives_array_args then + table.insert(call_args, 'args') end - if fn.arena_return then - output:write(', arena') + for _, a in ipairs(args) do + table.insert(call_args, a) + end + + if fn.receives_arena then + table.insert(call_args, 'arena') end if fn.has_lua_imp then - if #args > 0 then - output:write(', NULL') - else - output:write('NULL') - end + table.insert(call_args, 'NULL') + end + + if fn.can_fail then + table.insert(call_args, 'error') end + output:write(table.concat(call_args, ', ')) + output:write(');\n') + if fn.can_fail then -- if the function can fail, also pass a pointer to the local error object - if #args > 0 then - output:write(', error);\n') - else - output:write('error);\n') - end -- and check for the error output:write('\n if (ERROR_SET(error)) {') output:write('\n goto cleanup;') output:write('\n }\n') - else - output:write(');\n') end - if fn.return_type ~= 'void' then - output:write('\n ret = '..string.upper(real_type(fn.return_type))..'_OBJ(rv);') + local ret_type = real_type(fn.return_type) + if string.match(ret_type, '^KeyDict_') then + local table = string.sub(ret_type, 9) .. '_table' + output:write( + '\n ret = DICTIONARY_OBJ(api_keydict_to_dict(&rv, ' + .. table + .. ', ARRAY_SIZE(' + .. table + .. '), arena));' + ) + elseif ret_type ~= 'void' then + output:write('\n ret = ' .. string.upper(real_type(fn.return_type)) .. '_OBJ(rv);') end - output:write('\n\ncleanup:'); + output:write('\n\ncleanup:') - output:write('\n return ret;\n}\n\n'); + output:write('\n return ret;\n}\n\n') end end local remote_fns = {} -for _,fn in ipairs(functions) do +for _, fn in ipairs(functions) do if fn.remote then remote_fns[fn.name] = fn end end -remote_fns.redraw = {impl_name="ui_client_redraw", fast=true} +remote_fns.redraw = { impl_name = 'ui_client_redraw', fast = true } local names = vim.tbl_keys(remote_fns) table.sort(names) -local hashorder, hashfun = hashy.hashy_hash("msgpack_rpc_get_handler_for", names, function (idx) - return "method_handlers["..idx.."].name" +local hashorder, hashfun = hashy.hashy_hash('msgpack_rpc_get_handler_for', names, function(idx) + return 'method_handlers[' .. idx .. '].name' end) -output:write("const MsgpackRpcRequestHandler method_handlers[] = {\n") +output:write('const MsgpackRpcRequestHandler method_handlers[] = {\n') for n, name in ipairs(hashorder) do local fn = remote_fns[name] - fn.handler_id = n-1 - output:write(' { .name = "'..name..'", .fn = handle_'.. (fn.impl_name or fn.name).. - ', .fast = '..tostring(fn.fast)..', .arena_return = '..tostring(not not fn.arena_return)..'},\n') + fn.handler_id = n - 1 + output:write( + ' { .name = "' + .. name + .. '", .fn = handle_' + .. (fn.impl_name or fn.name) + .. ', .fast = ' + .. tostring(fn.fast) + .. ', .ret_alloc = ' + .. tostring(not not fn.ret_alloc) + .. '},\n' + ) end -output:write("};\n\n") +output:write('};\n\n') output:write(hashfun) output:close() functions.keysets = keysets -local mpack_output = io.open(mpack_outputf, 'wb') +local mpack_output = assert(io.open(mpack_outputf, 'wb')) mpack_output:write(mpack.encode(functions)) mpack_output:close() local function include_headers(output_handle, headers_to_include) for i = 1, #headers_to_include do if headers_to_include[i]:sub(-12) ~= '.generated.h' then - output_handle:write('\n#include "nvim/'..headers_to_include[i]..'"') + output_handle:write('\n#include "nvim/' .. headers_to_include[i] .. '"') end end end -local function write_shifted_output(_, str) +local function write_shifted_output(str, ...) str = str:gsub('\n ', '\n') str = str:gsub('^ ', '') str = str:gsub(' +$', '') - output:write(str) + output:write(string.format(str, ...)) end -- start building lua output -output = io.open(lua_c_bindings_outputf, 'wb') +output = assert(io.open(lua_c_bindings_outputf, 'wb')) output:write([[ #include <lua.h> @@ -557,6 +724,7 @@ output:write([[ #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/func_attr.h" +#include "nvim/globals.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/private/dispatch.h" @@ -572,42 +740,51 @@ local lua_c_functions = {} local function process_function(fn) local lua_c_function_name = ('nlua_api_%s'):format(fn.name) - write_shifted_output(output, string.format([[ + write_shifted_output( + [[ static int %s(lua_State *lstate) { Error err = ERROR_INIT; + Arena arena = ARENA_EMPTY; char *err_param = 0; if (lua_gettop(lstate) != %i) { api_set_error(&err, kErrorTypeValidation, "Expected %i argument%s"); goto exit_0; } - ]], lua_c_function_name, #fn.parameters, #fn.parameters, - (#fn.parameters == 1) and '' or 's')) + ]], + lua_c_function_name, + #fn.parameters, + #fn.parameters, + (#fn.parameters == 1) and '' or 's' + ) lua_c_functions[#lua_c_functions + 1] = { - binding=lua_c_function_name, - api=fn.name + binding = lua_c_function_name, + api = fn.name, } if not fn.fast then - write_shifted_output(output, string.format([[ + write_shifted_output( + [[ if (!nlua_is_deferred_safe()) { return luaL_error(lstate, e_luv_api_disabled, "%s"); } - ]], fn.name)) + ]], + fn.name + ) end if fn.textlock then - write_shifted_output(output, [[ + write_shifted_output([[ if (text_locked()) { - api_set_error(&err, kErrorTypeException, "%s", get_text_locked_msg()); + api_set_error(&err, kErrorTypeException, "%%s", get_text_locked_msg()); goto exit_0; } ]]) elseif fn.textlock_allow_cmdwin then - write_shifted_output(output, [[ + write_shifted_output([[ if (textlock != 0 || expr_map_locked()) { - api_set_error(&err, kErrorTypeException, "%s", e_textlock); + api_set_error(&err, kErrorTypeException, "%%s", e_textlock); goto exit_0; } ]]) @@ -615,48 +792,69 @@ local function process_function(fn) local cparams = '' local free_code = {} - for j = #fn.parameters,1,-1 do + for j = #fn.parameters, 1, -1 do local param = fn.parameters[j] local cparam = string.format('arg%u', j) local param_type = real_type(param[1]) - local lc_param_type = real_type(param[1]):lower() - local extra = param_type == "Dictionary" and "false, " or "" - if param[1] == "Object" or param[1] == "DictionaryOf(LuaRef)" then - extra = "true, " + local extra = param_type == 'Dictionary' and 'false, ' or '' + local arg_free_code = '' + if param[1] == 'Object' then + extra = 'true, ' + arg_free_code = 'api_luarefs_free_object(' .. cparam .. ');' + elseif param[1] == 'DictionaryOf(LuaRef)' then + extra = 'true, ' + arg_free_code = 'api_luarefs_free_dict(' .. cparam .. ');' + elseif param[1] == 'LuaRef' then + arg_free_code = 'api_free_luaref(' .. cparam .. ');' end local errshift = 0 local seterr = '' if string.match(param_type, '^KeyDict_') then - write_shifted_output(output, string.format([[ - %s %s = { 0 }; nlua_pop_keydict(lstate, &%s, %s_get_field, &err_param, &err);]], param_type, cparam, cparam, param_type)) - cparam = '&'..cparam + write_shifted_output( + [[ + %s %s = KEYDICT_INIT; + nlua_pop_keydict(lstate, &%s, %s_get_field, &err_param, &arena, &err); + ]], + param_type, + cparam, + cparam, + param_type + ) + cparam = '&' .. cparam errshift = 1 -- free incomplete dict on error + arg_free_code = 'api_luarefs_free_keydict(' + .. cparam + .. ', ' + .. string.sub(param_type, 9) + .. '_table);' else - write_shifted_output(output, string.format([[ - const %s %s = nlua_pop_%s(lstate, %s&err);]], param[1], cparam, param_type, extra)) - seterr = [[ - err_param = "]]..param[2]..[[";]] + write_shifted_output( + [[ + const %s %s = nlua_pop_%s(lstate, %s&arena, &err);]], + param[1], + cparam, + param_type, + extra + ) + seterr = '\n err_param = "' .. param[2] .. '";' end - write_shifted_output(output, string.format([[ + write_shifted_output([[ + + if (ERROR_SET(&err)) {]] .. seterr .. [[ - if (ERROR_SET(&err)) {]]..seterr..[[ goto exit_%u; } - ]], #fn.parameters - j + errshift)) - free_code[#free_code + 1] = ('api_free_%s(%s);'):format( - lc_param_type, cparam) + ]], #fn.parameters - j + errshift) + free_code[#free_code + 1] = arg_free_code cparams = cparam .. ', ' .. cparams end if fn.receives_channel_id then cparams = 'LUA_INTERNAL_CALL, ' .. cparams end - if fn.arena_return then + if fn.receives_arena then cparams = cparams .. '&arena, ' - write_shifted_output(output, [[ - Arena arena = ARENA_EMPTY; - ]]) end if fn.has_lua_imp then @@ -673,28 +871,28 @@ local function process_function(fn) local rev_i = #free_code - i + 1 local code = free_code[rev_i] if i == 1 and not string.match(real_type(fn.parameters[1][1]), '^KeyDict_') then - free_at_exit_code = free_at_exit_code .. ('\n %s'):format(code) + free_at_exit_code = free_at_exit_code .. ('\n %s'):format(code) else - free_at_exit_code = free_at_exit_code .. ('\n exit_%u:\n %s'):format( - rev_i, code) + free_at_exit_code = free_at_exit_code .. ('\nexit_%u:\n %s'):format(rev_i, code) end end local err_throw_code = [[ - exit_0: - if (ERROR_SET(&err)) { - luaL_where(lstate, 1); - if (err_param) { - lua_pushstring(lstate, "Invalid '"); - lua_pushstring(lstate, err_param); - lua_pushstring(lstate, "': "); - } - lua_pushstring(lstate, err.msg); - api_clear_error(&err); - lua_concat(lstate, err_param ? 5 : 2); - return lua_error(lstate); +exit_0: + arena_mem_free(arena_finish(&arena)); + if (ERROR_SET(&err)) { + luaL_where(lstate, 1); + if (err_param) { + lua_pushstring(lstate, "Invalid '"); + lua_pushstring(lstate, err_param); + lua_pushstring(lstate, "': "); } - ]] + lua_pushstring(lstate, err.msg); + api_clear_error(&err); + lua_concat(lstate, err_param ? 5 : 2); + return lua_error(lstate); + } +]] local return_type if fn.return_type ~= 'void' then if fn.return_type:match('^ArrayOf') then @@ -702,45 +900,68 @@ local function process_function(fn) else return_type = fn.return_type end - local free_retval - if fn.arena_return then - free_retval = "arena_mem_free(arena_finish(&arena));" - else - free_retval = "api_free_"..return_type:lower().."(ret);" + local free_retval = '' + if fn.ret_alloc then + free_retval = ' api_free_' .. return_type:lower() .. '(ret);' end - write_shifted_output(output, string.format([[ - const %s ret = %s(%s); - ]], fn.return_type, fn.name, cparams)) + write_shifted_output(' %s ret = %s(%s);\n', fn.return_type, fn.name, cparams) + local ret_type = real_type(fn.return_type) + local ret_mode = (ret_type == 'Object') and '&' or '' if fn.has_lua_imp then -- only push onto the Lua stack if we haven't already - write_shifted_output(output, string.format([[ + write_shifted_output(string.format( + [[ if (lua_gettop(lstate) == 0) { - nlua_push_%s(lstate, ret, true); + nlua_push_%s(lstate, %sret, true); } - ]], return_type)) + ]], + return_type, + ret_mode + )) + elseif string.match(ret_type, '^KeyDict_') then + write_shifted_output( + ' nlua_push_keydict(lstate, &ret, %s_table);\n', + string.sub(ret_type, 9) + ) else local special = (fn.since ~= nil and fn.since < 11) - write_shifted_output(output, string.format([[ - nlua_push_%s(lstate, ret, %s); - ]], return_type, tostring(special))) + write_shifted_output( + ' nlua_push_%s(lstate, %sret, %s);\n', + return_type, + ret_mode, + tostring(special) + ) end - write_shifted_output(output, string.format([[ + -- NOTE: we currently assume err_throw needs nothing from arena + write_shifted_output( + + [[ %s %s %s return 1; - ]], free_retval, free_at_exit_code, err_throw_code)) + ]], + free_retval, + free_at_exit_code, + err_throw_code + ) else - write_shifted_output(output, string.format([[ + write_shifted_output( + [[ %s(%s); %s %s return 0; - ]], fn.name, cparams, free_at_exit_code, err_throw_code)) + ]], + fn.name, + cparams, + free_at_exit_code, + err_throw_code + ) end - write_shifted_output(output, [[ + write_shifted_output([[ } ]]) end @@ -751,18 +972,25 @@ for _, fn in ipairs(functions) do end end -output:write(string.format([[ -void nlua_add_api_functions(lua_State *lstate); // silence -Wmissing-prototypes +output:write(string.format( + [[ +void nlua_add_api_functions(lua_State *lstate) + REAL_FATTR_NONNULL_ALL; void nlua_add_api_functions(lua_State *lstate) - FUNC_ATTR_NONNULL_ALL { lua_createtable(lstate, 0, %u); -]], #lua_c_functions)) +]], + #lua_c_functions +)) for _, func in ipairs(lua_c_functions) do - output:write(string.format([[ + output:write(string.format( + [[ lua_pushcfunction(lstate, &%s); - lua_setfield(lstate, -2, "%s");]], func.binding, func.api)) + lua_setfield(lstate, -2, "%s");]], + func.binding, + func.api + )) end output:write([[ |