2014-04-10 15:51:44 -03:00
lpeg = require ( ' lpeg ' )
msgpack = require ( ' cmsgpack ' )
2014-05-07 17:59:16 -03:00
-- 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
2014-04-10 15:51:44 -03:00
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 )
2014-09-12 11:24:01 -03:00
typed_container =
( P ( ' ArrayOf( ' ) + P ( ' DictionaryOf( ' ) ) * ( ( any - P ( ' ) ' ) ) ^ 1 ) * P ( ' ) ' )
c_id = (
typed_container +
( letter * ( alpha ^ 0 ) )
)
2014-04-10 15:51:44 -03:00
c_void = P ( ' void ' )
c_param_type = (
2014-05-07 17:59:16 -03:00
( ( P ( ' Error ' ) * fill * P ( ' * ' ) * fill ) * Cc ( ' error ' ) ) +
2014-05-16 08:00:31 -03:00
( C ( c_id ) * ( ws ^ 1 ) )
2014-04-10 15:51:44 -03:00
)
2014-05-07 17:59:16 -03:00
c_type = ( C ( c_void ) * ( ws ^ 1 ) ) + c_param_type
2014-04-10 15:51:44 -03:00
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 (
2014-05-07 17:59:16 -03:00
Cg ( c_type , ' return_type ' ) * Cg ( c_id , ' name ' ) *
fill * P ( ' ( ' ) * fill * Cg ( c_params , ' parameters ' ) * fill * P ( ' ) ' ) *
2014-04-10 15:51:44 -03:00
fill * P ( ' ; ' )
)
grammar = Ct ( ( c_proto + c_comment + c_preproc + ws ) ^ 1 )
2014-05-07 17:59:16 -03:00
-- we need at least 2 arguments since the last one is the output file
assert ( # arg >= 1 )
2014-09-11 21:56:52 -03:00
functions = { }
2014-09-07 20:40:07 -03:00
2014-05-07 17:59:16 -03:00
-- 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 ]
2014-04-10 15:51:44 -03:00
2014-05-07 17:59:16 -03:00
-- read each input file, parse and append to the api metadata
2014-09-11 21:56:52 -03:00
for i = 1 , # arg - 1 do
2014-05-07 17:59:16 -03:00
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 ]
2014-04-10 15:51:44 -03:00
2014-05-07 17:59:16 -03:00
local input = io.open ( full_path , ' rb ' )
local tmp = grammar : match ( input : read ( ' *all ' ) )
for i = 1 , # tmp do
2014-09-11 21:56:52 -03:00
functions [ # functions + 1 ] = tmp [ i ]
2014-09-03 17:19:55 -03:00
local fn = tmp [ i ]
2014-05-28 08:42:08 -03:00
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
2014-05-07 17:59:16 -03:00
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
input : close ( )
2014-04-10 15:51:44 -03:00
end
2014-05-07 17:59:16 -03:00
-- start building the output
2014-04-10 15:51:44 -03:00
output = io.open ( outputf , ' wb ' )
output : write ( [ [
# include < stdbool.h >
# include < stdint.h >
2014-06-23 00:40:35 -05:00
# include < assert.h >
2014-04-10 15:51:44 -03:00
# include < msgpack.h >
2014-08-28 15:46:21 -03:00
# include " nvim/map.h "
2014-07-08 11:03:18 -03:00
# include " nvim/log.h "
2014-09-03 11:46:35 -03:00
# include " nvim/vim.h "
2014-05-12 15:50:37 +02:00
# include " nvim/os/msgpack_rpc.h "
2014-06-23 11:42:29 -03:00
# include " nvim/os/msgpack_rpc_helpers.h "
2014-06-23 11:52:42 -03:00
# include " nvim/api/private/helpers.h "
2014-09-12 11:24:01 -03:00
# include " nvim/api/private/defs.h "
2014-05-07 17:59:16 -03:00
] ] )
for i = 1 , # headers do
2014-05-10 17:24:13 +04:00
if headers [ i ] : sub ( - 12 ) ~= ' .generated.h ' then
output : write ( ' \n #include "nvim/ ' .. headers [ i ] .. ' " ' )
end
2014-05-07 17:59:16 -03:00
end
output : write ( [ [
2014-04-10 15:51:44 -03:00
2014-09-11 21:56:52 -03:00
static const uint8_t msgpack_metadata [ ] = {
2014-04-10 15:51:44 -03:00
] ] )
2014-05-07 17:59:16 -03:00
-- serialize the API metadata using msgpack and embed into the resulting
2014-06-18 20:04:39 -03:00
-- binary for easy querying by clients
2014-09-11 21:56:52 -03:00
packed = msgpack.pack ( functions )
2014-04-10 15:51:44 -03:00
for i = 1 , # packed do
output : write ( string.byte ( packed , i ) .. ' , ' )
if i % 10 == 0 then
output : write ( ' \n ' )
end
end
output : write ( [ [
} ;
2014-09-11 21:56:52 -03:00
void msgpack_rpc_init_function_metadata ( Dictionary * metadata )
{
msgpack_unpacked unpacked ;
msgpack_unpacked_init ( & unpacked ) ;
assert ( msgpack_unpack_next ( & unpacked ,
( const char * ) msgpack_metadata ,
sizeof ( msgpack_metadata ) ,
NULL ) == MSGPACK_UNPACK_SUCCESS ) ;
Object functions ;
msgpack_rpc_to_object ( & unpacked.data , & functions ) ;
msgpack_unpacked_destroy ( & unpacked ) ;
PUT ( * metadata , " functions " , functions ) ;
}
2014-04-10 15:51:44 -03:00
] ] )
2014-09-12 11:24:01 -03:00
local function real_type ( type )
local rv = type
if typed_container : match ( rv ) then
if rv : match ( ' Array ' ) then
rv = ' Array '
else
rv = ' Dictionary '
end
end
return rv
end
2014-09-11 21:56:52 -03:00
-- 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 ]
2014-04-10 15:51:44 -03:00
local args = { }
2014-05-07 17:59:16 -03:00
2014-06-18 20:04:39 -03:00
output : write ( ' static Object handle_ ' .. fn.name .. ' (uint64_t channel_id, msgpack_object *req, Error *error) ' )
output : write ( ' \n { ' )
2014-07-08 11:03:18 -03:00
output : write ( ' \n DLOG("Received msgpack-rpc call to ' .. fn.name .. ' (request id: %" PRIu64 ")", req->via.array.ptr[1].via.u64); ' )
2014-05-07 17:59:16 -03:00
-- Declare/initialize variables that will hold converted arguments
for j = 1 , # fn.parameters do
local param = fn.parameters [ j ]
local converted = ' arg_ ' .. j
2014-09-12 11:24:01 -03:00
output : write ( ' \n ' .. param [ 1 ] .. ' ' .. converted .. ' api_init_ ' .. string.lower ( real_type ( param [ 1 ] ) ) .. ' ; ' )
2014-05-07 17:59:16 -03:00
end
output : write ( ' \n ' )
2014-07-07 11:51:07 -03:00
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 ' )
2014-05-07 17:59:16 -03:00
-- 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
2014-09-12 11:24:01 -03:00
convert_arg = ' msgpack_rpc_to_ ' .. real_type ( param [ 1 ] ) : lower ( )
2014-06-18 20:04:39 -03:00
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 ' )
2014-05-07 17:59:16 -03:00
args [ # args + 1 ] = converted
2014-04-10 15:51:44 -03:00
end
2014-06-18 20:04:39 -03:00
2014-05-07 17:59:16 -03:00
-- function call
2014-04-10 15:51:44 -03:00
local call_args = table.concat ( args , ' , ' )
2014-06-18 20:04:39 -03:00
output : write ( ' \n ' )
2014-05-07 17:59:16 -03:00
if fn.return_type ~= ' void ' then
-- has a return value, prefix the call with a declaration
output : write ( fn.return_type .. ' rv = ' )
end
2014-05-28 08:42:08 -03:00
-- 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
2014-06-17 14:18:56 -03:00
output : write ( ' channel_id ' )
2014-05-28 08:42:08 -03:00
end
else
output : write ( call_args )
end
2014-05-07 17:59:16 -03:00
if fn.can_fail then
-- if the function can fail, also pass a pointer to the local error object
if # args > 0 then
2014-06-23 11:52:42 -03:00
output : write ( ' , error); \n ' )
2014-05-07 17:59:16 -03:00
else
2014-06-23 11:52:42 -03:00
output : write ( ' error); \n ' )
2014-04-10 15:51:44 -03:00
end
2014-05-07 17:59:16 -03:00
-- and check for the error
2014-06-18 20:04:39 -03:00
output : write ( ' \n if (error->set) { ' )
output : write ( ' \n goto cleanup; ' )
output : write ( ' \n } \n ' )
2014-05-07 17:59:16 -03:00
else
output : write ( ' ); \n ' )
end
2014-06-23 11:52:42 -03:00
if fn.return_type ~= ' void ' then
2014-09-12 11:24:01 -03:00
output : write ( ' \n Object ret = ' .. string.upper ( real_type ( fn.return_type ) ) .. ' _OBJ(rv); ' )
2014-05-07 17:59:16 -03:00
end
-- Now generate the cleanup label for freeing memory allocated for the
-- arguments
2014-06-18 20:04:39 -03:00
output : write ( ' \n \n cleanup: ' ) ;
2014-05-07 17:59:16 -03:00
for j = 1 , # fn.parameters do
local param = fn.parameters [ j ]
2014-09-12 11:24:01 -03:00
output : write ( ' \n api_free_ ' .. string.lower ( real_type ( param [ 1 ] ) ) .. ' (arg_ ' .. j .. ' ); ' )
2014-06-18 20:04:39 -03:00
end
if fn.return_type ~= ' void ' then
output : write ( ' \n return ret; \n } \n \n ' ) ;
else
output : write ( ' \n return NIL; \n } \n \n ' ) ;
2014-04-10 15:51:44 -03:00
end
end
2014-05-07 17:59:16 -03:00
2014-08-28 15:46:21 -03:00
-- Generate a function that initializes method names with handler functions
2014-06-18 20:04:39 -03:00
output : write ( [ [
2014-09-03 14:26:16 -03:00
static Map ( String , rpc_method_handler_fn ) * methods = NULL ;
2014-08-28 15:46:21 -03:00
void msgpack_rpc_init ( void )
{
2014-09-03 14:26:16 -03:00
methods = map_new ( String , rpc_method_handler_fn ) ( ) ;
2014-08-28 15:46:21 -03:00
] ] )
2014-09-03 14:26:16 -03:00
-- Keep track of the maximum method name length in order to avoid walking
-- strings longer than that when searching for a method handler
2014-08-28 15:46:21 -03:00
local max_fname_len = 0
2014-09-11 21:56:52 -03:00
for i = 1 , # functions do
local fn = functions [ i ]
2014-09-03 14:26:16 -03:00
output : write ( ' map_put(String, rpc_method_handler_fn)(methods, ' ..
' (String) {.data = " ' .. fn.name .. ' ", ' ..
' .size = sizeof(" ' .. fn.name .. ' ") - 1}, handle_ ' ..
fn.name .. ' ); \n ' )
2014-08-28 15:46:21 -03:00
if # fn.name > max_fname_len then
max_fname_len = # fn.name
end
end
output : write ( ' \n } \n \n ' )
output : write ( [ [
2014-06-18 20:04:39 -03:00
Object msgpack_rpc_dispatch ( uint64_t channel_id ,
msgpack_object * req ,
Error * error )
{
2014-08-28 15:46:21 -03:00
msgpack_object method = req -> via.array . ptr [ 2 ] ;
2014-09-03 14:26:16 -03:00
rpc_method_handler_fn handler = NULL ;
2014-08-28 15:46:21 -03:00
2014-09-03 14:26:16 -03:00
if ( method.type == MSGPACK_OBJECT_BIN || method.type == MSGPACK_OBJECT_STR ) {
] ] )
output : write ( ' handler = map_get(String, rpc_method_handler_fn) ' )
output : write ( ' (methods, (String){.data=(char *)method.via.bin.ptr, ' )
output : write ( ' .size=min(method.via.bin.size, ' .. max_fname_len .. ' )}); \n ' )
output : write ( [ [
2014-08-28 15:46:21 -03:00
}
2014-09-03 14:26:16 -03:00
if ( ! handler ) {
2014-09-03 14:55:55 -03:00
handler = msgpack_rpc_handle_missing_method ;
2014-09-03 14:26:16 -03:00
}
return handler ( channel_id , req , error ) ;
}
2014-04-10 15:51:44 -03:00
] ] )
2014-06-18 20:04:39 -03:00
2014-04-10 15:51:44 -03:00
output : close ( )