span: fix wrong next1 feature index in dual loop
[vpp.git] / vppapigen / pyvppapigen.py
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2016 Cisco and/or its affiliates.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #
16
17 from __future__ import print_function
18 import argparse, sys, os, importlib, pprint
19
20 parser = argparse.ArgumentParser(description='VPP Python API generator')
21 parser.add_argument('-i', '--input', action="store", dest="inputfile", type=argparse.FileType('r'))
22 parser.add_argument('-c', '--cfile', action="store")
23 args = parser.parse_args()
24
25 #
26 # Read API definitions file into vppapidefs
27 #
28 exec(args.inputfile.read())
29
30 # https://docs.python.org/3/library/struct.html
31 format_struct = {'u8': 'B',
32                  'u16' : 'H',
33                  'u32' : 'I',
34                  'i32' : 'i',
35                  'u64' : 'Q',
36                  'f64' : 'd',
37                  'vl_api_fib_path_t' : 'IIBBBBBBBBBBBBBBBBBBBBB',
38                  'vl_api_ip4_fib_counter_t' : 'IBQQ',
39                  'vl_api_ip6_fib_counter_t' : 'QQBQQ',
40                  'vl_api_lisp_adjacency_t' : 'B' * 35
41                  };
42 #
43 # NB: If new types are introduced in vpe.api, these must be updated.
44 #
45 type_size = {'u8':   1,
46              'u16' : 2,
47              'u32' : 4,
48              'i32' : 4,
49              'u64' : 8,
50              'f64' : 8,
51              'vl_api_fib_path_t' : 29,
52              'vl_api_ip4_fib_counter_t' : 21,
53              'vl_api_ip6_fib_counter_t' : 33,
54              'vl_api_lisp_adjacency_t' : 35
55 };
56
57 def eprint(*args, **kwargs):
58     print(*args, file=sys.stderr, **kwargs)
59
60 def get_args(t):
61     argslist = []
62     for i in t:
63         if i[1][0] == '_':
64             argslist.append(i[1][1:])
65         else:
66             argslist.append(i[1])
67
68     return argslist
69
70 def get_pack(f):
71     zeroarray = False
72     bytecount = 0
73     pack = ''
74     elements = 1
75     if len(f) is 3 or len(f) is 4:
76         size = type_size[f[0]]
77         bytecount += size * int(f[2])
78         # Check if we have a zero length array
79         if f[2] == '0':
80             # If len 3 zero array
81             elements = 0;
82             pack += format_struct[f[0]]
83             bytecount = size
84         elif size == 1:
85             n = f[2] * size
86             pack += str(n) + 's'
87         else:
88             pack += format_struct[f[0]] * int(f[2])
89             elements = int(f[2])
90     else:
91         bytecount += type_size[f[0]]
92         pack += format_struct[f[0]]
93     return (pack, elements, bytecount)
94
95
96 '''
97 def get_reply_func(f):
98     if f['name']+'_reply' in func_name:
99         return func_name[f['name']+'_reply']
100     if f['name'].find('_dump') > 0:
101         r = f['name'].replace('_dump','_details')
102         if r in func_name:
103             return func_name[r]
104     return None
105 '''
106
107 def footer_print():
108     print('''
109 def msg_id_base_set(b):
110     global base
111     base = b
112
113 import os
114 name = os.path.splitext(os.path.basename(__file__))[0]
115     ''')
116     print(u"plugin_register(name, api_func_table, api_name_to_id,", vl_api_version, ", msg_id_base_set)")
117
118 def api_table_print(name, i):
119     msg_id_in = 'VL_API_' + name.upper()
120     fstr = name + '_decode'
121     print('api_func_table.append(' + fstr + ')')
122     print('api_name_to_id["' + msg_id_in + '"] =', i)
123     print('')
124
125
126 def encode_print(name, id, t):
127     args = get_args(t)
128
129     if name.find('_dump') > 0:
130         multipart = True
131     else:
132         multipart = False
133
134     if len(args) < 4:
135         print(u"def", name + "(async = False):")
136     else:
137         print(u"def", name + "(" + ', '.join(args[3:]) + ", async = False):")
138     print(u"    global base")
139     print(u"    context = get_context(base + " + id + ")")
140
141     print('''
142     results_prepare(context)
143     waiting_for_reply_set()
144     ''')
145     if multipart == True:
146         print(u"    results_more_set(context)")
147
148     t = list(t)
149
150     # only the last field can be a variable-length-array
151     # it can either be 0, or a string
152     # first, deal with all the other fields
153     pack = '>' + ''.join([get_pack(f)[0] for f in t[:-1]])
154
155     # named variable-length-array
156     if len(t[-1]) == 4 and t[-1][2] == '0' and t[-1][3] == t[-2][1]:
157         print(u"    vpp_api.write(pack('" + pack + "', base + "
158               + id + ", 0, context, " + ', '.join(args[3:-2] + ["len(" + args[-1] + ")"])
159               + ") + " + args[-1] + ")")
160
161     # unnamed variable-length-array
162     elif len(t[-1]) >= 3 and t[-1][2] == '0':
163         print(u"    vpp_api.write(pack('" + pack + "', base + " +
164               id + ", 0, context, " + ', '.join(args[3:-1]) + ") + "
165               + args[-1] + ")")
166
167
168     # not a variable-length-array
169     else:
170         pack += get_pack(t[-1])[0]
171         print(u"    vpp_api.write(pack('" + pack + "', base + " + id +
172               ", 0, context, " + ', '.join(args[3:]) + "))")
173
174     if multipart == True:
175         print(
176             u"    vpp_api.write(pack('>HII', VL_API_CONTROL_PING, 0, context))")
177
178     print('''
179     if not async:
180         results_event_wait(context, 5)
181         return results_get(context)
182     return context
183     ''')
184
185 def get_normal_pack(t, i, pack, offset):
186     while t:
187         f = t.pop(0)
188         i += 1
189         if len(f) >= 3:
190             return t, i, pack, offset, f
191         p, elements, size = get_pack(f)
192         pack += p
193         offset += size
194     return t, i, pack, offset, None
195
196 def decode_print(name, t):
197     #
198     # Generate code for each element
199     #
200     print(u'def ' + name + u'_decode(msg):')
201     total = 0
202     args = get_args(t)
203     print(u"    n = namedtuple('" + name + "', '" + ', '.join(args) + "')")
204     print(u"    res = []")
205
206     pack = '>'
207     start = 0
208     end = 0
209     offset = 0
210     t = list(t)
211     i = 0
212     while t:
213         t, i, pack, offset, array = get_normal_pack(t, i, pack, offset)
214         if array:
215             p, elements, size = get_pack(array)
216
217             # Byte string
218             if elements > 0 and type_size[array[0]] == 1:
219                 pack += p
220                 offset += size * elements
221                 continue
222
223             # Dump current pack string
224             if pack != '>':
225                 print(u"    tr = unpack_from('" + pack + "', msg[" + str(start) + ":])")
226                 print(u"    res.extend(list(tr))")
227                 start += offset
228             pack = '>'
229
230             if elements == 0:
231                 # This has to be the last element
232                 if len(array) == 3:
233                     print(u"    res.append(msg[" + str(offset) + ":])")
234                     if len(t) > 0:
235                         eprint('WARNING: Variable length array must be last element in message', name, array)
236
237                     continue
238                 if size == 1 or len(p) == 1:
239                     # Do it as a bytestring.
240                     if p == 'B':
241                         p = 's'
242                     # XXX: Assume that length parameter is the previous field. Add validation.
243                     print(u"    c = res[" + str(i - 2) + "]")
244                     print(u"    tr = unpack_from('>' + str(c) + '" + p + "', msg[" + str(start) + ":])")
245                     print(u"    res.append(tr)")
246                     continue
247                 print(u"    tr2 = []")
248                 print(u"    offset = " + str(total))
249                 print(u"    for j in range(res[" + str(i - 2) + "]):")
250                 print(u"        tr2.append(unpack_from('>" + p + "', msg[" + str(start) + ":], offset))")
251                 print(u"        offset += " + str(size))
252                 print(u"    res.append(tr2)")
253                 continue
254
255             # Missing something!!
256             print(u"    tr = unpack_from('>" + p + "', msg[" + str(start) + ":])")
257             start += size
258
259             print(u"    res.append(tr)")
260
261     if pack != '>':
262         print(u"    tr = unpack_from('" + pack + "', msg[" + str(start) + ":])")
263         print(u"    res.extend(list(tr))")
264     print(u"    return n._make(res)")
265     print('')
266
267 #
268 # Generate the main Python file
269 #
270 def main():
271     print('''
272 #
273 # AUTO-GENERATED FILE. PLEASE DO NOT EDIT.
274 #
275 from vpp_api_base import *
276 from struct import *
277 from collections import namedtuple
278 import vpp_api
279 api_func_table = []
280 api_name_to_id = {}
281     ''')
282
283     for i, a in enumerate(messages):
284         name = a[0]
285         encode_print(name, str(i), a[1:])
286         decode_print(name, a[1:])
287         api_table_print(name, i)
288     footer_print()
289
290 if __name__ == "__main__":
291     main()