aboutsummaryrefslogtreecommitdiff
path: root/scripts/msgpack-gen.lua
diff options
context:
space:
mode:
authorThiago de Arruda <tpadilha84@gmail.com>2014-04-10 15:51:44 -0300
committerThiago de Arruda <tpadilha84@gmail.com>2014-04-11 14:07:45 -0300
commit35ff53c6b0a98f6376cca5f42ab96499e4b476b6 (patch)
tree6f3a406846587d3c3ff250b36bfe6ff51e0d5c69 /scripts/msgpack-gen.lua
parent6eeb006c4a225d4b1b9bb7d887759bcb491b2c97 (diff)
downloadrneovim-35ff53c6b0a98f6376cca5f42ab96499e4b476b6.tar.gz
rneovim-35ff53c6b0a98f6376cca5f42ab96499e4b476b6.tar.bz2
rneovim-35ff53c6b0a98f6376cca5f42ab96499e4b476b6.zip
Add `msgpack_rpc_dispatch`/metadata generator
This adds a lua script which parses the contents of 'api.h'. After the api is parsed into a metadata table. After that, it will generate: - A msgpack blob for the metadata table. This msgpack object contains everything scripting engines need to generate their own wrappers for the remote API. - The `msgpack_rpc_dispatch` function, which takes care of validating msgpack requests, converting arguments to C types and passing control to the appropriate 'api.h' function. The result is then serialized back to msgpack and returned to the client. This approach was used because: - It automatically modifies `msgpack_rpc_dispatch` to reflect API changes. - Scripting engines that generate remote call wrappers using the msgpack metadata will also adapt automatically to API changes
Diffstat (limited to 'scripts/msgpack-gen.lua')
-rw-r--r--scripts/msgpack-gen.lua135
1 files changed, 135 insertions, 0 deletions
diff --git a/scripts/msgpack-gen.lua b/scripts/msgpack-gen.lua
new file mode 100644
index 0000000000..58e13dd548
--- /dev/null
+++ b/scripts/msgpack-gen.lua
@@ -0,0 +1,135 @@
+lpeg = require('lpeg')
+msgpack = require('cmsgpack')
+
+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_raw = P('char') * fill * P('*')
+c_int = P('uint32_t')
+c_array = c_raw * fill * P('*') * Cc('array')
+c_param_type = (
+ (c_array * Cc('array') * fill) +
+ (c_raw * Cc('raw') * fill) +
+ (c_int * Cc('integer') * (ws ^ 1))
+ )
+c_type = (c_void * Cc('none') * (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, 'rtype') * Cg(c_id, 'fname') *
+ fill * P('(') * fill * Cg(c_params, 'params') * fill * P(')') *
+ fill * P(';')
+ )
+grammar = Ct((c_proto + c_comment + c_preproc + ws) ^ 1)
+
+inputf = assert(arg[1])
+outputf = assert(arg[2])
+
+input = io.open(inputf, 'rb')
+api = grammar:match(input:read('*all'))
+input:close()
+
+-- assign a unique integer id for each api function
+for i = 1, #api do
+ api[i].id = i
+end
+
+output = io.open(outputf, 'wb')
+
+output:write([[
+#include <stdbool.h>
+#include <stdint.h>
+#include <msgpack.h>
+
+#include "api.h"
+#include "msgpack_rpc.h"
+
+static const uint8_t msgpack_metadata[] = {
+
+]])
+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([[
+};
+
+bool msgpack_rpc_dispatch(msgpack_object *req, msgpack_packer *res)
+{
+ uint32_t method_id = (uint32_t)req->via.u64;
+
+ switch (method_id) {
+ case 0:
+ msgpack_rpc_response(req, res);
+ msgpack_pack_nil(res);
+ // The result is the `msgpack_metadata` byte array
+ msgpack_pack_raw(res, sizeof(msgpack_metadata));
+ msgpack_pack_raw_body(res, msgpack_metadata, sizeof(msgpack_metadata));
+ return true;
+]])
+
+for i = 1, #api do
+ local fn
+ local args = {}
+ fn = api[i]
+ output:write('\n case '..fn.id..':')
+ for j = 1, #fn.params do
+ local expected, convert, param
+ local idx = tostring(j - 1)
+ param = fn.params[j]
+ ref = '(req->via.array.ptr[3].via.array.ptr + '..idx..')'
+ -- decide which validation/conversion to use for this argument
+ if param[1] == 'array' then
+ expected = 'MSGPACK_OBJECT_ARRAY'
+ convert = 'msgpack_rpc_array_argument'
+ elseif param[1] == 'raw' then
+ expected = 'MSGPACK_OBJECT_RAW'
+ convert = 'msgpack_rpc_raw_argument'
+ elseif param[1] == 'integer' then
+ expected = 'MSGPACK_OBJECT_POSITIVE_INTEGER'
+ convert = 'msgpack_rpc_integer_argument'
+ end
+ output:write('\n if ('..ref..'->type != '..expected..') {')
+ output:write('\n return msgpack_rpc_error(req, res, "Wrong argument types");')
+ output:write('\n }')
+ table.insert(args, convert..'('..ref..')')
+ end
+ local call_args = table.concat(args, ', ')
+ -- convert the result back to msgpack
+ if fn.rtype == 'none' then
+ output:write('\n '..fn.fname..'('..call_args..');')
+ output:write('\n return msgpack_rpc_void_result(req, res);\n')
+ else
+ if fn.rtype == 'array' then
+ convert = 'msgpack_rpc_array_result'
+ elseif fn.rtype == 'raw' then
+ convert = 'msgpack_rpc_raw_result'
+ elseif fn.rtype == 'integer' then
+ convert = 'msgpack_rpc_integer_result'
+ end
+ output:write('\n return '..convert..'('..fn.fname..'('..call_args..'), req, res);\n')
+ end
+end
+output:write([[
+ default:
+ abort();
+ return false;
+ }
+}
+]])
+output:close()