mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Problem: 1. The main log routine does not protect itself against recursion. log_lock() doesn't guard against recursion, it would deadlock... 2.22b52dd462(#11501) regressed6f27f5ef91(#10172), because set_init_1..process_spawn tries to log (see backtrace below), but the mutex isn't initialized yet. Even if the mutex were valid, we don't want early logging to fallback to stderr because that can break embedders when stdio is used for RPC. frame 1: 0x00000001001d54f4 nvim`open_log_file at log.c:205:7 frame 2: 0x00000001001d5390 nvim`logmsg(log_level=1, context="UI: ", func_name=0x0000000000000000, line_num=-1, eol=true, fmt="win_viewport") at log.c:150:20 frame : 0x000000010039aea2 nvim`ui_call_win_viewport(grid=2, win=1000, topline=0, botline=1, curline=0, curcol=0, line_count=1) at ui_events_call.generated.h:321:3 frame 4: 0x00000001003dfefc nvim`ui_ext_win_viewport(wp=0x0000000101816400) at window.c:939:5 frame 5: 0x00000001003ec5b4 nvim`win_ui_flush at window.c:7303:7 frame 6: 0x00000001003a04c0 nvim`ui_flush at ui.c:508:3 frame 7: 0x00000001002966ba nvim`do_os_system(argv=0x0000600000c0c000, input=0x0000000000000000, len=0, output=0x0000000000000000, nread=0x00007ff7bfefe830, silent=false, forward_output=false) at shell.c:894:3 frame 8: 0x0000000100295f68 nvim`os_call_shell(cmd="unset nonomatch; vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >/var/folders/gk/3tttv_md06987tlwpyp62jrw0000gn/T/nvimwwvwfD/0 ~foo", opts=kShellOptExpand | kShellOptSilent | kShellOptHideMess, extra_args=0x0000000000000000) at shell.c:663:18 frame 9: 0x0000000100295845 nvim`call_shell(cmd="unset nonomatch; vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >/var/folders/gk/3tttv_md06987tlwpyp62jrw0000gn/T/nvimwwvwfD/0 ~foo", opts=kShellOptExpand | kShellOptSilent | kShellOptHideMess, extra_shell_arg=0x0000000000000000) at shell.c:712:14 frame 10: 0x0000000100294c6f nvim`os_expand_wildcards(num_pat=1, pat=0x00007ff7bfefeb20, num_file=0x00007ff7bfefee58, file=0x00007ff7bfefee60, flags=43) at shell.c:328:7 ... frame 23: 0x000000010028ccef nvim`expand_env_esc(srcp=",~foo", dst="~foo", dstlen=4094, esc=false, one=false, prefix=0x0000000000000000) at env.c:673:17 frame 24: 0x000000010026fdd5 nvim`option_expand(opt_idx=29, val=",~foo") at option.c:1950:3 frame 25: 0x000000010026f129 nvim`set_init_1(clean_arg=false) at option.c:558:19 frame 26: 0x00000001001ea25e nvim`early_init(paramp=0x00007ff7bfeff5f0) at main.c:198:3 frame 27: 0x00000001001ea6bf nvim`main(argc=1, argv=0x00007ff7bfeff848) at main.c:255:3 Solution: 1. Check for recursion, show "internal error" message. - FUTURE: when "remote TUI" is merged, can we remove log_lock()? 2. Skip logging if log_init wasn't called yet.
562 lines
18 KiB
Lua
562 lines
18 KiB
Lua
local mpack = require('mpack')
|
|
|
|
-- we need at least 4 arguments since the last two are output files
|
|
if arg[1] == '--help' then
|
|
print('Usage: genmsgpack.lua args')
|
|
print('Args: 1: source directory')
|
|
print(' 2: dispatch output file (dispatch_wrappers.generated.h)')
|
|
print(' 3: functions metadata output file (funcs_metadata.generated.h)')
|
|
print(' 4: API metadata output file (api_metadata.mpack)')
|
|
print(' 5: lua C bindings output file (lua_api_c_bindings.generated.c)')
|
|
print(' rest: C files where API functions are defined')
|
|
end
|
|
assert(#arg >= 4)
|
|
local functions = {}
|
|
|
|
local nvimdir = arg[1]
|
|
package.path = nvimdir .. '/?.lua;' .. package.path
|
|
|
|
_G.vim = loadfile(nvimdir..'/../../runtime/lua/vim/shared.lua')()
|
|
|
|
local hashy = require'generators.hashy'
|
|
|
|
-- names of all headers relative to the source root (for inclusion in the
|
|
-- generated file)
|
|
local headers = {}
|
|
|
|
-- output h file with generated dispatch functions
|
|
local dispatch_outputf = arg[2]
|
|
-- output h file with packed metadata
|
|
local funcs_metadata_outputf = arg[3]
|
|
-- output metadata mpack file, for use by other build scripts
|
|
local mpack_outputf = arg[4]
|
|
local lua_c_bindings_outputf = arg[5]
|
|
|
|
-- set of function names, used to detect duplicates
|
|
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
|
|
|
|
-- read each input file, parse and append to the api metadata
|
|
for i = 6, #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 = io.open(full_path, 'rb')
|
|
|
|
local tmp = c_grammar.grammar:match(input:read('*all'))
|
|
for j = 1, #tmp do
|
|
local fn = tmp[j]
|
|
local public = startswith(fn.name, "nvim_") or fn.deprecated_since
|
|
if public and not fn.noexport then
|
|
functions[#functions + 1] = tmp[j]
|
|
function_names[fn.name] = true
|
|
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
|
|
end
|
|
end
|
|
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__") 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__") 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
|
|
exported_functions[#exported_functions+1] = f_exported
|
|
end
|
|
end
|
|
|
|
|
|
-- 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.pack(exported_functions)
|
|
local dump_bin_array = require("generators.dump_bin_array")
|
|
dump_bin_array(funcs_metadata_output, 'funcs_metadata', packed)
|
|
funcs_metadata_output:close()
|
|
|
|
-- start building the dispatch wrapper output
|
|
local output = io.open(dispatch_outputf, 'wb')
|
|
|
|
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, Error *error)')
|
|
output:write('\n{')
|
|
output:write('\n#if MIN_LOG_LEVEL <= LOGLVL_DBG')
|
|
output:write('\n logmsg(LOGLVL_DBG, "RPC: ", NULL, -1, true, "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')
|
|
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')
|
|
|
|
-- 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.check_textlock then
|
|
output:write('\n if (textlock != 0) {')
|
|
output:write('\n api_set_error(error, kErrorTypeException, "%s", e_secure);')
|
|
output:write('\n goto cleanup;')
|
|
output:write('\n }\n')
|
|
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 = ')
|
|
end
|
|
|
|
-- write the function name and the opening parenthesis
|
|
output:write(fn.name..'(')
|
|
|
|
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, '..call_args)
|
|
else
|
|
output:write('channel_id')
|
|
end
|
|
else
|
|
output:write(call_args)
|
|
end
|
|
|
|
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);')
|
|
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 hashorder, hashfun = hashy.hashy_hash("msgpack_rpc_get_handler_for", vim.tbl_keys(remote_fns), function (idx)
|
|
return "method_handlers["..idx.."].name"
|
|
end)
|
|
|
|
output:write("static const MsgpackRpcRequestHandler method_handlers[] = {\n")
|
|
for _, name in ipairs(hashorder) do
|
|
local fn = remote_fns[name]
|
|
output:write(' { .name = "'..name..'", .fn = handle_'.. (fn.impl_name or fn.name)..
|
|
', .fast = '..tostring(fn.fast)..'},\n')
|
|
end
|
|
output:write("};\n\n")
|
|
output:write(hashfun)
|
|
|
|
output:close()
|
|
|
|
local mpack_output = io.open(mpack_outputf, 'wb')
|
|
mpack_output:write(mpack.pack(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(str)
|
|
end
|
|
|
|
-- start building lua output
|
|
output = io.open(lua_c_bindings_outputf, 'wb')
|
|
|
|
output:write([[
|
|
// This is an open source non-commercial project. Dear PVS-Studio, please check
|
|
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
|
#include <lua.h>
|
|
#include <lualib.h>
|
|
#include <lauxlib.h>
|
|
|
|
#include "nvim/func_attr.h"
|
|
#include "nvim/api/private/defs.h"
|
|
#include "nvim/api/private/helpers.h"
|
|
#include "nvim/lua/converter.h"
|
|
#include "nvim/lua/executor.h"
|
|
]])
|
|
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(output, string.format([[
|
|
|
|
static int %s(lua_State *lstate)
|
|
{
|
|
Error err = ERROR_INIT;
|
|
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(output, string.format([[
|
|
if (!nlua_is_deferred_safe()) {
|
|
return luaL_error(lstate, e_luv_api_disabled, "%s");
|
|
}
|
|
]], fn.name))
|
|
end
|
|
|
|
if fn.check_textlock then
|
|
write_shifted_output(output, [[
|
|
if (textlock != 0) {
|
|
api_set_error(&err, kErrorTypeException, "%s", e_secure);
|
|
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 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, "
|
|
end
|
|
local errshift = 0
|
|
if string.match(param_type, '^KeyDict_') then
|
|
write_shifted_output(output, string.format([[
|
|
%s %s = { 0 }; nlua_pop_keydict(lstate, &%s, %s_get_field, %s&err);]], param_type, cparam, cparam, param_type, extra))
|
|
cparam = '&'..cparam
|
|
errshift = 1 -- free incomplete dict on error
|
|
else
|
|
write_shifted_output(output, string.format([[
|
|
const %s %s = nlua_pop_%s(lstate, %s&err);]], param[1], cparam, param_type, extra))
|
|
end
|
|
|
|
write_shifted_output(output, string.format([[
|
|
|
|
if (ERROR_SET(&err)) {
|
|
goto exit_%u;
|
|
}
|
|
|
|
]], #fn.parameters - j + errshift))
|
|
free_code[#free_code + 1] = ('api_free_%s(%s);'):format(
|
|
lc_param_type, cparam)
|
|
cparams = cparam .. ', ' .. cparams
|
|
end
|
|
if fn.receives_channel_id then
|
|
cparams = 'LUA_INTERNAL_CALL, ' .. cparams
|
|
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 .. ('\n exit_%u:\n %s'):format(
|
|
rev_i, code)
|
|
end
|
|
end
|
|
local err_throw_code = [[
|
|
|
|
exit_0:
|
|
if (ERROR_SET(&err)) {
|
|
luaL_where(lstate, 1);
|
|
lua_pushstring(lstate, err.msg);
|
|
api_clear_error(&err);
|
|
lua_concat(lstate, 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
|
|
write_shifted_output(output, string.format([[
|
|
const %s ret = %s(%s);
|
|
nlua_push_%s(lstate, ret, true);
|
|
api_free_%s(ret);
|
|
%s
|
|
%s
|
|
return 1;
|
|
]], fn.return_type, fn.name, cparams, return_type, return_type:lower(),
|
|
free_at_exit_code, err_throw_code))
|
|
else
|
|
write_shifted_output(output, string.format([[
|
|
%s(%s);
|
|
%s
|
|
%s
|
|
return 0;
|
|
]], fn.name, cparams, free_at_exit_code, err_throw_code))
|
|
end
|
|
write_shifted_output(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); // silence -Wmissing-prototypes
|
|
void nlua_add_api_functions(lua_State *lstate)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
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()
|