aboutsummaryrefslogtreecommitdiff
path: root/scripts/msgpack-gen.lua
blob: 58e13dd54898ae51a2350e8b7f5cf6166286d2e9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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()