mirror of
https://github.com/neovim/neovim.git
synced 2024-12-20 03:05:11 -07:00
cf30837951
Also changed the default log level to INFO so developers won't end up with big log files without asking explicitly(DLOG statements were placed in really "hot" code)
260 lines
8.1 KiB
Lua
260 lines
8.1 KiB
Lua
lpeg = require('lpeg')
|
|
msgpack = require('cmsgpack')
|
|
|
|
-- lpeg grammar for building api metadata from a set of header files. It
|
|
-- ignores comments and preprocessor commands and parses a very small subset
|
|
-- of C prototypes with a limited set of types
|
|
P, R, S = lpeg.P, lpeg.R, lpeg.S
|
|
C, Ct, Cc, Cg = lpeg.C, lpeg.Ct, lpeg.Cc, lpeg.Cg
|
|
|
|
any = P(1) -- (consume one character)
|
|
letter = R('az', 'AZ') + S('_$')
|
|
alpha = letter + R('09')
|
|
nl = P('\n')
|
|
not_nl = any - nl
|
|
ws = S(' \t') + nl
|
|
fill = ws ^ 0
|
|
c_comment = P('//') * (not_nl ^ 0)
|
|
c_preproc = P('#') * (not_nl ^ 0)
|
|
c_id = letter * (alpha ^ 0)
|
|
c_void = P('void')
|
|
c_param_type = (
|
|
((P('Error') * fill * P('*') * fill) * Cc('error')) +
|
|
(C(c_id) * (ws ^ 1))
|
|
)
|
|
c_type = (C(c_void) * (ws ^ 1)) + c_param_type
|
|
c_param = Ct(c_param_type * C(c_id))
|
|
c_param_list = c_param * (fill * (P(',') * fill * c_param) ^ 0)
|
|
c_params = Ct(c_void + c_param_list)
|
|
c_proto = Ct(
|
|
Cg(c_type, 'return_type') * Cg(c_id, 'name') *
|
|
fill * P('(') * fill * Cg(c_params, 'parameters') * fill * P(')') *
|
|
fill * P(';')
|
|
)
|
|
grammar = Ct((c_proto + c_comment + c_preproc + ws) ^ 1)
|
|
|
|
-- we need at least 2 arguments since the last one is the output file
|
|
assert(#arg >= 1)
|
|
-- api metadata
|
|
api = {
|
|
functions = {},
|
|
-- Helpers for object-oriented languages
|
|
classes = {'Buffer', 'Window', 'Tabpage'}
|
|
}
|
|
-- names of all headers relative to the source root(for inclusion in the
|
|
-- generated file)
|
|
headers = {}
|
|
-- output file(dispatch function + metadata serialized with msgpack)
|
|
outputf = arg[#arg]
|
|
|
|
-- read each input file, parse and append to the api metadata
|
|
for i = 1, #arg - 1 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 = grammar:match(input:read('*all'))
|
|
for i = 1, #tmp do
|
|
api.functions[#api.functions + 1] = tmp[i]
|
|
local fn_id = #api.functions
|
|
local fn = api.functions[fn_id]
|
|
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
|
|
-- assign a unique integer id for each api function
|
|
fn.id = fn_id
|
|
end
|
|
input:close()
|
|
end
|
|
|
|
|
|
-- start building the output
|
|
output = io.open(outputf, 'wb')
|
|
|
|
output:write([[
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <assert.h>
|
|
#include <msgpack.h>
|
|
|
|
#include "nvim/log.h"
|
|
#include "nvim/os/msgpack_rpc.h"
|
|
#include "nvim/os/msgpack_rpc_helpers.h"
|
|
#include "nvim/api/private/helpers.h"
|
|
]])
|
|
|
|
for i = 1, #headers do
|
|
if headers[i]:sub(-12) ~= '.generated.h' then
|
|
output:write('\n#include "nvim/'..headers[i]..'"')
|
|
end
|
|
end
|
|
|
|
output:write([[
|
|
|
|
|
|
const uint8_t msgpack_metadata[] = {
|
|
|
|
]])
|
|
-- serialize the API metadata using msgpack and embed into the resulting
|
|
-- binary for easy querying by clients
|
|
packed = msgpack.pack(api)
|
|
for i = 1, #packed do
|
|
output:write(string.byte(packed, i)..', ')
|
|
if i % 10 == 0 then
|
|
output:write('\n ')
|
|
end
|
|
end
|
|
output:write([[
|
|
};
|
|
const unsigned int msgpack_metadata_size = sizeof(msgpack_metadata);
|
|
|
|
]])
|
|
|
|
-- start the handler functions. First handler (method_id=0) is reserved for
|
|
-- querying the metadata, usually it is the first function called by clients.
|
|
-- Visit each function metadata to build the handler function with code
|
|
-- generated for validating arguments and calling to the real API.
|
|
for i = 1, #api.functions do
|
|
local fn = api.functions[i]
|
|
local args = {}
|
|
|
|
output:write('static Object handle_'..fn.name..'(uint64_t channel_id, msgpack_object *req, Error *error)')
|
|
output:write('\n{')
|
|
output:write('\n DLOG("Received msgpack-rpc call to '..fn.name..'(request id: %" PRIu64 ")", req->via.array.ptr[1].via.u64);')
|
|
-- Declare/initialize variables that will hold converted arguments
|
|
for j = 1, #fn.parameters do
|
|
local param = fn.parameters[j]
|
|
local converted = 'arg_'..j
|
|
output:write('\n '..param[1]..' '..converted..' msgpack_rpc_init_'..string.lower(param[1])..';')
|
|
end
|
|
output:write('\n')
|
|
output:write('\n if (req->via.array.ptr[3].via.array.size != '..#fn.parameters..') {')
|
|
output:write('\n snprintf(error->msg, sizeof(error->msg), "Wrong number of arguments: expecting '..#fn.parameters..' but got %u", req->via.array.ptr[3].via.array.size);')
|
|
output:write('\n error->set = true;')
|
|
output:write('\n goto cleanup;')
|
|
output:write('\n }\n')
|
|
|
|
-- Validation/conversion for each argument
|
|
for j = 1, #fn.parameters do
|
|
local converted, convert_arg, param, arg
|
|
param = fn.parameters[j]
|
|
arg = '(req->via.array.ptr[3].via.array.ptr + '..(j - 1)..')'
|
|
converted = 'arg_'..j
|
|
convert_arg = 'msgpack_rpc_to_'..string.lower(param[1])
|
|
output:write('\n if (!'..convert_arg..'('..arg..', &'..converted..')) {')
|
|
output:write('\n snprintf(error->msg, sizeof(error->msg), "Wrong type for argument '..j..', expecting '..param[1]..'");')
|
|
output:write('\n error->set = true;')
|
|
output:write('\n goto cleanup;')
|
|
output:write('\n }\n')
|
|
args[#args + 1] = converted
|
|
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 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) {')
|
|
output:write('\n goto cleanup;')
|
|
output:write('\n }\n')
|
|
else
|
|
output:write(');\n')
|
|
end
|
|
|
|
if fn.return_type ~= 'void' then
|
|
output:write('\n Object ret = '..string.upper(fn.return_type)..'_OBJ(rv);')
|
|
end
|
|
-- Now generate the cleanup label for freeing memory allocated for the
|
|
-- arguments
|
|
output:write('\n\ncleanup:');
|
|
|
|
for j = 1, #fn.parameters do
|
|
local param = fn.parameters[j]
|
|
output:write('\n msgpack_rpc_free_'..string.lower(param[1])..'(arg_'..j..');')
|
|
end
|
|
if fn.return_type ~= 'void' then
|
|
output:write('\n return ret;\n}\n\n');
|
|
else
|
|
output:write('\n return NIL;\n}\n\n');
|
|
end
|
|
end
|
|
|
|
output:write([[
|
|
static Object handle_missing_method(uint64_t channel_id,
|
|
msgpack_object *req,
|
|
Error *error)
|
|
{
|
|
snprintf(error->msg, sizeof(error->msg), "Invalid function id");
|
|
error->set = true;
|
|
return NIL;
|
|
}
|
|
|
|
]])
|
|
|
|
-- Generate the table of handler functions indexed by method id
|
|
output:write([[
|
|
static const rpc_method_handler_fn rpc_method_handlers[] = {
|
|
[0] = (rpc_method_handler_fn)NULL]])
|
|
|
|
for i = 1, #api.functions do
|
|
local fn = api.functions[i]
|
|
output:write(',\n ['..i..'] = handle_'..fn.name..'')
|
|
end
|
|
output:write('\n};\n\n')
|
|
|
|
output:write([[
|
|
Object msgpack_rpc_dispatch(uint64_t channel_id,
|
|
uint64_t method_id,
|
|
msgpack_object *req,
|
|
Error *error)
|
|
{
|
|
]])
|
|
output:write('\n // method_id=0 is specially handled')
|
|
output:write('\n assert(method_id > 0);')
|
|
output:write('\n');
|
|
output:write('\n rpc_method_handler_fn handler = (method_id <= '..#api.functions..') ?')
|
|
output:write('\n rpc_method_handlers[method_id] : handle_missing_method;')
|
|
output:write('\n return handler(channel_id, req, error);')
|
|
output:write('\n}\n')
|
|
|
|
output:close()
|