]> git.proxmox.com Git - mirror_frr.git/blob - python/clidef.py
Merge pull request #1044 from donaldsharp/combination
[mirror_frr.git] / python / clidef.py
1 # FRR CLI preprocessor (DEFPY)
2 #
3 # Copyright (C) 2017 David Lamparter for NetDEF, Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the Free
7 # Software Foundation; either version 2 of the License, or (at your option)
8 # any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 # more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; see the file COPYING; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
19 import clippy, traceback, sys, os
20 from collections import OrderedDict
21 from functools import reduce
22 from pprint import pprint
23 from string import Template
24 from io import StringIO
25
26 # the various handlers generate output C code for a particular type of
27 # CLI token, choosing the most useful output C type.
28
29 class RenderHandler(object):
30 def __init__(self, token):
31 pass
32 def combine(self, other):
33 if type(self) == type(other):
34 return other
35 return StringHandler(None)
36
37 deref = ''
38 drop_str = False
39 canfail = True
40
41 class StringHandler(RenderHandler):
42 argtype = 'const char *'
43 decl = Template('const char *$varname = NULL;')
44 code = Template('$varname = argv[_i]->arg;')
45 drop_str = True
46 canfail = False
47
48 class LongHandler(RenderHandler):
49 argtype = 'long'
50 decl = Template('long $varname = 0;')
51 code = Template('''\
52 char *_end;
53 $varname = strtol(argv[_i]->arg, &_end, 10);
54 _fail = (_end == argv[_i]->arg) || (*_end != '\\0');''')
55
56 # A.B.C.D/M (prefix_ipv4) and
57 # X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a
58 # struct prefix:
59
60 class PrefixBase(RenderHandler):
61 def combine(self, other):
62 if type(self) == type(other):
63 return other
64 if isinstance(other, PrefixBase):
65 return PrefixGenHandler(None)
66 return StringHandler(None)
67 deref = '&'
68 class Prefix4Handler(PrefixBase):
69 argtype = 'const struct prefix_ipv4 *'
70 decl = Template('struct prefix_ipv4 $varname = { };')
71 code = Template('_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);')
72 class Prefix6Handler(PrefixBase):
73 argtype = 'const struct prefix_ipv6 *'
74 decl = Template('struct prefix_ipv6 $varname = { };')
75 code = Template('_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);')
76 class PrefixEthHandler(PrefixBase):
77 argtype = 'struct prefix_eth *'
78 decl = Template('struct prefix_eth $varname = { };')
79 code = Template('_fail = !str2prefix_eth(argv[_i]->arg, &$varname);')
80 class PrefixGenHandler(PrefixBase):
81 argtype = 'const struct prefix *'
82 decl = Template('struct prefix $varname = { };')
83 code = Template('_fail = !str2prefix(argv[_i]->arg, &$varname);')
84
85 # same for IP addresses. result is union sockunion.
86 class IPBase(RenderHandler):
87 def combine(self, other):
88 if type(self) == type(other):
89 return other
90 if type(other) in [IP4Handler, IP6Handler, IPGenHandler]:
91 return IPGenHandler(None)
92 return StringHandler(None)
93 class IP4Handler(IPBase):
94 argtype = 'struct in_addr'
95 decl = Template('struct in_addr $varname = { INADDR_ANY };')
96 code = Template('_fail = !inet_aton(argv[_i]->arg, &$varname);')
97 class IP6Handler(IPBase):
98 argtype = 'struct in6_addr'
99 decl = Template('struct in6_addr $varname = IN6ADDR_ANY_INIT;')
100 code = Template('_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);')
101 class IPGenHandler(IPBase):
102 argtype = 'const union sockunion *'
103 decl = Template('''union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;''')
104 code = Template('''\
105 if (argv[_i]->text[0] == 'X') {
106 s__$varname.sa.sa_family = AF_INET6;
107 _fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr);
108 $varname = &s__$varname;
109 } else {
110 s__$varname.sa.sa_family = AF_INET;
111 _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
112 $varname = &s__$varname;
113 }''')
114
115 def mix_handlers(handlers):
116 def combine(a, b):
117 if a is None:
118 return b
119 return a.combine(b)
120 return reduce(combine, handlers, None)
121
122 handlers = {
123 'WORD_TKN': StringHandler,
124 'VARIABLE_TKN': StringHandler,
125 'RANGE_TKN': LongHandler,
126 'IPV4_TKN': IP4Handler,
127 'IPV4_PREFIX_TKN': Prefix4Handler,
128 'IPV6_TKN': IP6Handler,
129 'IPV6_PREFIX_TKN': Prefix6Handler,
130 'MAC_TKN': PrefixEthHandler,
131 'MAC_PREFIX_TKN': PrefixEthHandler,
132 }
133
134 # core template invoked for each occurence of DEFPY.
135 #
136 # the "#if $..." bits are there to keep this template unified into one
137 # common form, without requiring a more advanced template engine (e.g.
138 # jinja2)
139 templ = Template('''/* $fnname => "$cmddef" */
140 DEFUN_CMD_FUNC_DECL($fnname)
141 #define funcdecl_$fnname static int ${fnname}_magic(\\
142 const struct cmd_element *self __attribute__ ((unused)),\\
143 struct vty *vty __attribute__ ((unused)),\\
144 int argc __attribute__ ((unused)),\\
145 struct cmd_token *argv[] __attribute__ ((unused))$argdefs)
146 funcdecl_$fnname;
147 DEFUN_CMD_FUNC_TEXT($fnname)
148 {
149 #if $nonempty /* anything to parse? */
150 int _i;
151 #if $canfail /* anything that can fail? */
152 unsigned _fail = 0, _failcnt = 0;
153 #endif
154 $argdecls
155 for (_i = 0; _i < argc; _i++) {
156 if (!argv[_i]->varname)
157 continue;
158 #if $canfail /* anything that can fail? */
159 _fail = 0;
160 #endif
161 $argblocks
162 #if $canfail /* anything that can fail? */
163 if (_fail)
164 vty_out (vty, "%% invalid input for %s: %s\\n",
165 argv[_i]->varname, argv[_i]->arg);
166 _failcnt += _fail;
167 #endif
168 }
169 #if $canfail /* anything that can fail? */
170 if (_failcnt)
171 return CMD_WARNING;
172 #endif
173 #endif
174 return ${fnname}_magic(self, vty, argc, argv$arglist);
175 }
176
177 ''')
178
179 # invoked for each named parameter
180 argblock = Template('''
181 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
182 $code
183 }''')
184
185 def process_file(fn, ofd, dumpfd, all_defun):
186 filedata = clippy.parse(fn)
187
188 for entry in filedata['data']:
189 if entry['type'] == 'DEFPY' or (all_defun and entry['type'].startswith('DEFUN')):
190 cmddef = entry['args'][2]
191 for i in cmddef:
192 assert i.startswith('"') and i.endswith('"')
193 cmddef = ''.join([i[1:-1] for i in cmddef])
194
195 graph = clippy.Graph(cmddef)
196 args = OrderedDict()
197 for token, depth in clippy.graph_iterate(graph):
198 if token.type not in handlers:
199 continue
200 if token.varname is None:
201 continue
202 arg = args.setdefault(token.varname, [])
203 arg.append(handlers[token.type](token))
204
205 #print('-' * 76)
206 #pprint(entry)
207 #clippy.dump(graph)
208 #pprint(args)
209
210 params = { 'cmddef': cmddef, 'fnname': entry['args'][0][0] }
211 argdefs = []
212 argdecls = []
213 arglist = []
214 argblocks = []
215 doc = []
216 canfail = 0
217
218 def do_add(handler, varname, attr = ''):
219 argdefs.append(',\\\n\t%s %s%s' % (handler.argtype, varname, attr))
220 argdecls.append('\t%s\n' % (handler.decl.substitute({'varname': varname}).replace('\n', '\n\t')))
221 arglist.append(', %s%s' % (handler.deref, varname))
222 if attr == '':
223 at = handler.argtype
224 if not at.startswith('const '):
225 at = '. . . ' + at
226 doc.append('\t%-26s %s' % (at, varname))
227
228 for varname in args.keys():
229 handler = mix_handlers(args[varname])
230 #print(varname, handler)
231 if handler is None: continue
232 do_add(handler, varname)
233 code = handler.code.substitute({'varname': varname}).replace('\n', '\n\t\t\t')
234 if handler.canfail:
235 canfail = 1
236 strblock = ''
237 if not handler.drop_str:
238 do_add(StringHandler(None), '%s_str' % (varname), ' __attribute__ ((unused))')
239 strblock = '\n\t\t\t%s_str = argv[_i]->arg;' % (varname)
240 argblocks.append(argblock.substitute({'varname': varname, 'strblock': strblock, 'code': code}))
241
242 if dumpfd is not None:
243 if len(arglist) > 0:
244 dumpfd.write('"%s":\n%s\n\n' % (cmddef, '\n'.join(doc)))
245 else:
246 dumpfd.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef))
247
248 params['argdefs'] = ''.join(argdefs)
249 params['argdecls'] = ''.join(argdecls)
250 params['arglist'] = ''.join(arglist)
251 params['argblocks'] = ''.join(argblocks)
252 params['canfail'] = canfail
253 params['nonempty'] = len(argblocks)
254 ofd.write(templ.substitute(params))
255
256 if __name__ == '__main__':
257 import argparse
258
259 argp = argparse.ArgumentParser(description = 'FRR CLI preprocessor in Python')
260 argp.add_argument('--all-defun', action = 'store_const', const = True,
261 help = 'process DEFUN() statements in addition to DEFPY()')
262 argp.add_argument('--show', action = 'store_const', const = True,
263 help = 'print out list of arguments and types for each definition')
264 argp.add_argument('-o', type = str, metavar = 'OUTFILE',
265 help = 'output C file name')
266 argp.add_argument('cfile', type = str)
267 args = argp.parse_args()
268
269 dumpfd = None
270 if args.o is not None:
271 ofd = StringIO()
272 if args.show:
273 dumpfd = sys.stdout
274 else:
275 ofd = sys.stdout
276 if args.show:
277 dumpfd = sys.stderr
278
279 process_file(args.cfile, ofd, dumpfd, args.all_defun)
280
281 if args.o is not None:
282 clippy.wrdiff(args.o, ofd, [args.cfile, os.path.realpath(__file__)])