Files
neovim/src/nvim/generators/gen_api_dispatch.lua
bfredl 3711a0387a refactor(build): make all generated c files headers
There's no "rule" or bad practice or whatever that says we cannot
generate c files. it is is just that we have ~20 generated headers
and ~2 generated sources and there is nothing in these two generated
source files which sets them aparts. Lua bindings are not different from
rpc bindings, and pathdef is not different from versiondef.

So to simplify build logic and ease the future port to build.zig,
streamline the build to only have generated headers, no direct generated
.c files.

Also "nlua_add_api_functions" had its prototype duplicated twice which
defeated the point of having mandatory prototypes (one source of truth).
2024-04-27 18:59:48 +02:00

984 lines
28 KiB
Lua

local mpack = vim.mpack
local hashy = require 'generators.hashy'
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 (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 = {}
-- names of all headers relative to the source root (for inclusion in the
-- generated file)
local headers = {}
-- set of function names, used to detect duplicates
local function_names = {}
local c_grammar = require('generators.c_grammar')
local startswith = vim.startswith
local function add_function(fn)
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
-- function receives the "args" as a parameter
fn.receives_array_args = true
-- remove the args parameter
table.remove(fn.parameters, 2)
end
if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then
-- this function should receive the channel id
fn.receives_channel_id = true
-- remove the parameter since it won't be passed by the api client
table.remove(fn.parameters, 1)
end
if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'error' then
-- function can fail if the last parameter type is 'Error'
fn.can_fail = true
-- remove the error parameter, msgpack has it's own special field
-- for specifying errors
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
local keysets = {}
local function add_keyset(val)
local keys = {}
local types = {}
local is_set_name = 'is_set__' .. val.keyset_name .. '_'
local has_optional = false
for i, field in ipairs(val.fields) do
if field.type ~= 'Object' then
types[field.name] = field.type
end
if field.name ~= is_set_name and field.type ~= 'OptionalKeys' then
table.insert(keys, field.name)
else
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)
elseif field.type ~= 'OptionalKeys' then
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,
})
end
local ui_options_text = nil
-- read each input file, parse and append to the api metadata
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]
local input = assert(io.open(full_path, 'rb'))
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)
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
local function shallowcopy(orig)
local copy = {}
for orig_key, orig_value in pairs(orig) do
copy[orig_key] = orig_value
end
return copy
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 ismethod = false
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')
os.exit(1)
end
f.since = tonumber(f.since)
if f.deprecated_since ~= nil then
f.deprecated_since = tonumber(f.deprecated_since)
end
if startswith(f.name, 'nvim_buf_') then
ismethod = true
elseif startswith(f.name, 'nvim_win_') then
ismethod = true
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
else
f.deprecated_since = tonumber(f.deprecated_since)
assert(f.deprecated_since == 1)
f.remote = true
f.since = 0
end
f.method = ismethod
local newname = deprecated_aliases[f.name]
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'
)
os.exit(1)
end
local newf = shallowcopy(f)
newf.name = newname
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'
end
newf.impl_name = f.name
newf.lua = false
newf.eval = false
newf.since = 0
newf.deprecated_since = 1
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_functions = {}
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
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] }
end
f_exported.parameters[i] = param
end
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 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 placeholder 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 = assert(io.open(dispatch_outputf, 'wb'))
local keysets_defs = assert(io.open(keysets_outputf, 'wb'))
-- ===========================================================================
-- NEW API FILES MUST GO HERE.
--
-- When creating a new API file, you must include it here,
-- so that the dispatcher can find the C functions that you are creating!
-- ===========================================================================
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/api/autocmd.h"
#include "nvim/api/buffer.h"
#include "nvim/api/command.h"
#include "nvim/api/deprecated.h"
#include "nvim/api/extmark.h"
#include "nvim/api/options.h"
#include "nvim/api/tabpage.h"
#include "nvim/api/ui.h"
#include "nvim/api/vim.h"
#include "nvim/api/vimscript.h"
#include "nvim/api/win_config.h"
#include "nvim/api/window.h"
#include "nvim/ui_client.h"
]])
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
-- some keys, like "register" are c keywords and get
-- escaped with a trailing _ in the struct.
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)
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'
end)
keysets_defs:write('extern KeySetLink ' .. k.name .. '_table[' .. (1 + #neworder) .. '];\n')
local function typename(type)
if type == 'HLGroupID' then
return 'kObjectTypeInteger'
elseif type ~= nil then
return 'kObjectType' .. type
else
return 'kObjectTypeNil'
end
end
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
.. ', '
.. (k.types[key] == 'HLGroupID' and 'true' or 'false')
.. '},\n'
)
end
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)
{
int hash = ]] .. k.name .. [[_hash(str, len);
if (hash == -1) {
return NULL;
}
return &]] .. k.name .. [[_table[hash];
}
]])
end
local function real_type(type)
local rv = type
local rmatch = string.match(type, 'Dict%(([_%w]+)%)')
if rmatch then
return 'KeyDict_' .. rmatch
elseif c_grammar.typed_container:match(rv) then
if rv:match('Array') then
rv = 'Array'
else
rv = 'Dictionary'
end
end
return rv
end
local function attr_name(rt)
if rt == 'Float' then
return 'floating'
else
return rt:lower()
end
end
-- start the handler functions. Visit each function metadata to build the
-- handler function with code generated for validating arguments and calling to
-- the real API.
for i = 1, #functions do
local fn = functions[i]
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('\n{')
output:write('\n#ifdef NVIM_LOG_DEBUG')
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 .. ';')
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 goto cleanup;')
output:write('\n }\n')
end
-- Validation/conversion for each argument
for j = 1, #fn.parameters do
local converted, param
param = fn.parameters[j]
converted = 'arg_' .. j
local rt = real_type(param[1])
if rt == 'Object' then
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 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 {')
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;'
)
else
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
-- 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;'
)
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;'
)
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;')
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 goto cleanup;')
output:write('\n }\n')
end
args[#args + 1] = converted
end
if fn.textlock then
output:write('\n if (text_locked()) {')
output:write('\n api_set_error(error, kErrorTypeException, "%s", get_text_locked_msg());')
output:write('\n goto cleanup;')
output:write('\n }\n')
elseif fn.textlock_allow_cmdwin then
output:write('\n if (textlock != 0 || expr_map_locked()) {')
output:write('\n api_set_error(error, kErrorTypeException, "%s", e_textlock);')
output:write('\n goto cleanup;')
output:write('\n }\n')
end
-- function call
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 = ')
end
-- write the function name and the opening parenthesis
output:write(fn.name .. '(')
local call_args = {}
if fn.receives_channel_id then
table.insert(call_args, 'channel_id')
end
if fn.receives_array_args then
table.insert(call_args, 'args')
end
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
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
-- and check for the error
output:write('\n if (ERROR_SET(error)) {')
output:write('\n goto cleanup;')
output:write('\n }\n')
end
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 return ret;\n}\n\n')
end
end
local remote_fns = {}
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 }
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'
end)
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)
.. ', .ret_alloc = '
.. tostring(not not fn.ret_alloc)
.. '},\n'
)
end
output:write('};\n\n')
output:write(hashfun)
output:close()
functions.keysets = keysets
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] .. '"')
end
end
end
local function write_shifted_output(str, ...)
str = str:gsub('\n ', '\n')
str = str:gsub('^ ', '')
str = str:gsub(' +$', '')
output:write(string.format(str, ...))
end
-- start building lua output
output = assert(io.open(lua_c_bindings_outputf, 'wb'))
include_headers(output, headers)
output:write('\n')
local lua_c_functions = {}
local function process_function(fn)
local lua_c_function_name = ('nlua_api_%s'):format(fn.name)
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_functions[#lua_c_functions + 1] = {
binding = lua_c_function_name,
api = fn.name,
}
if not fn.fast then
write_shifted_output(
[[
if (!nlua_is_deferred_safe()) {
return luaL_error(lstate, e_luv_api_disabled, "%s");
}
]],
fn.name
)
end
if fn.textlock then
write_shifted_output([[
if (text_locked()) {
api_set_error(&err, kErrorTypeException, "%%s", get_text_locked_msg());
goto exit_0;
}
]])
elseif fn.textlock_allow_cmdwin then
write_shifted_output([[
if (textlock != 0 || expr_map_locked()) {
api_set_error(&err, kErrorTypeException, "%%s", e_textlock);
goto exit_0;
}
]])
end
local cparams = ''
local free_code = {}
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 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(
[[
%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(
[[
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([[
if (ERROR_SET(&err)) {]] .. seterr .. [[
goto exit_%u;
}
]], #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.receives_arena then
cparams = cparams .. '&arena, '
end
if fn.has_lua_imp then
cparams = cparams .. 'lstate, '
end
if fn.can_fail then
cparams = cparams .. '&err'
else
cparams = cparams:gsub(', $', '')
end
local free_at_exit_code = ''
for i = 1, #free_code do
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)
else
free_at_exit_code = free_at_exit_code .. ('\nexit_%u:\n %s'):format(rev_i, code)
end
end
local err_throw_code = [[
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
return_type = 'Array'
else
return_type = fn.return_type
end
local free_retval = ''
if fn.ret_alloc then
free_retval = ' api_free_' .. return_type:lower() .. '(ret);'
end
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(string.format(
[[
if (lua_gettop(lstate) == 0) {
nlua_push_%s(lstate, %sret, kNluaPushSpecial | kNluaPushFreeRefs);
}
]],
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(
' nlua_push_%s(lstate, %sret, %s | kNluaPushFreeRefs);\n',
return_type,
ret_mode,
special and 'kNluaPushSpecial' or '0'
)
end
-- 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
)
else
write_shifted_output(
[[
%s(%s);
%s
%s
return 0;
]],
fn.name,
cparams,
free_at_exit_code,
err_throw_code
)
end
write_shifted_output([[
}
]])
end
for _, fn in ipairs(functions) do
if fn.lua or fn.name:sub(1, 4) == '_vim' then
process_function(fn)
end
end
output:write(string.format(
[[
void nlua_add_api_functions(lua_State *lstate)
{
lua_createtable(lstate, 0, %u);
]],
#lua_c_functions
))
for _, func in ipairs(lua_c_functions) do
output:write(string.format(
[[
lua_pushcfunction(lstate, &%s);
lua_setfield(lstate, -2, "%s");]],
func.binding,
func.api
))
end
output:write([[
lua_setfield(lstate, -2, "api");
}
]])
output:close()
keysets_defs:close()