]>
git.proxmox.com Git - mirror_frr.git/blob - python/clidef.py
1 # FRR CLI preprocessor (DEFPY)
3 # Copyright (C) 2017 David Lamparter for NetDEF, Inc.
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)
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
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
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
26 # the various handlers generate output C code for a particular type of
27 # CLI token, choosing the most useful output C type.
29 class RenderHandler(object):
30 def __init__(self
, token
):
32 def combine(self
, other
):
33 if type(self
) == type(other
):
35 return StringHandler(None)
42 class StringHandler(RenderHandler
):
43 argtype
= 'const char *'
44 decl
= Template('const char *$varname = NULL;')
45 code
= Template('$varname = (argv[_i]->type == WORD_TKN) ? argv[_i]->text : argv[_i]->arg;')
50 class LongHandler(RenderHandler
):
52 decl
= Template('long $varname = 0;')
55 $varname = strtol(argv[_i]->arg, &_end, 10);
56 _fail = (_end == argv[_i]->arg) || (*_end != '\\0');''')
58 # A.B.C.D/M (prefix_ipv4) and
59 # X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a
62 class PrefixBase(RenderHandler
):
63 def combine(self
, other
):
64 if type(self
) == type(other
):
66 if isinstance(other
, PrefixBase
):
67 return PrefixGenHandler(None)
68 return StringHandler(None)
70 class Prefix4Handler(PrefixBase
):
71 argtype
= 'const struct prefix_ipv4 *'
72 decl
= Template('struct prefix_ipv4 $varname = { };')
73 code
= Template('_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);')
74 class Prefix6Handler(PrefixBase
):
75 argtype
= 'const struct prefix_ipv6 *'
76 decl
= Template('struct prefix_ipv6 $varname = { };')
77 code
= Template('_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);')
78 class PrefixEthHandler(PrefixBase
):
79 argtype
= 'struct prefix_eth *'
80 decl
= Template('struct prefix_eth $varname = { };')
81 code
= Template('_fail = !str2prefix_eth(argv[_i]->arg, &$varname);')
82 class PrefixGenHandler(PrefixBase
):
83 argtype
= 'const struct prefix *'
84 decl
= Template('struct prefix $varname = { };')
85 code
= Template('_fail = !str2prefix(argv[_i]->arg, &$varname);')
87 # same for IP addresses. result is union sockunion.
88 class IPBase(RenderHandler
):
89 def combine(self
, other
):
90 if type(self
) == type(other
):
92 if type(other
) in [IP4Handler
, IP6Handler
, IPGenHandler
]:
93 return IPGenHandler(None)
94 return StringHandler(None)
95 class IP4Handler(IPBase
):
96 argtype
= 'struct in_addr'
97 decl
= Template('struct in_addr $varname = { INADDR_ANY };')
98 code
= Template('_fail = !inet_aton(argv[_i]->arg, &$varname);')
99 class IP6Handler(IPBase
):
100 argtype
= 'struct in6_addr'
101 decl
= Template('struct in6_addr $varname = {};')
102 code
= Template('_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);')
103 class IPGenHandler(IPBase
):
104 argtype
= 'const union sockunion *'
105 decl
= Template('''union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;''')
107 if (argv[_i]->text[0] == 'X') {
108 s__$varname.sa.sa_family = AF_INET6;
109 _fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr);
110 $varname = &s__$varname;
112 s__$varname.sa.sa_family = AF_INET;
113 _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
114 $varname = &s__$varname;
118 def mix_handlers(handlers
):
123 return reduce(combine
, handlers
, None)
126 'WORD_TKN': StringHandler
,
127 'VARIABLE_TKN': StringHandler
,
128 'RANGE_TKN': LongHandler
,
129 'IPV4_TKN': IP4Handler
,
130 'IPV4_PREFIX_TKN': Prefix4Handler
,
131 'IPV6_TKN': IP6Handler
,
132 'IPV6_PREFIX_TKN': Prefix6Handler
,
133 'MAC_TKN': PrefixEthHandler
,
134 'MAC_PREFIX_TKN': PrefixEthHandler
,
137 # core template invoked for each occurence of DEFPY.
139 # the "#if $..." bits are there to keep this template unified into one
140 # common form, without requiring a more advanced template engine (e.g.
142 templ
= Template('''/* $fnname => "$cmddef" */
143 DEFUN_CMD_FUNC_DECL($fnname)
144 #define funcdecl_$fnname static int ${fnname}_magic(\\
145 const struct cmd_element *self __attribute__ ((unused)),\\
146 struct vty *vty __attribute__ ((unused)),\\
147 int argc __attribute__ ((unused)),\\
148 struct cmd_token *argv[] __attribute__ ((unused))$argdefs)
150 DEFUN_CMD_FUNC_TEXT($fnname)
152 #if $nonempty /* anything to parse? */
154 #if $canfail /* anything that can fail? */
155 unsigned _fail = 0, _failcnt = 0;
158 for (_i = 0; _i < argc; _i++) {
159 if (!argv[_i]->varname)
161 #if $canfail /* anything that can fail? */
165 #if $canfail /* anything that can fail? */
167 vty_out (vty, "%% invalid input for %s: %s\\n",
168 argv[_i]->varname, argv[_i]->arg);
172 #if $canfail /* anything that can fail? */
178 return ${fnname}_magic(self, vty, argc, argv$arglist);
183 # invoked for each named parameter
184 argblock
= Template('''
185 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
189 def get_always_args(token
, always_args
, args
= [], stack
= []):
192 if token
.type == 'END_TKN':
193 for arg
in list(always_args
):
195 always_args
.remove(arg
)
198 stack
= stack
+ [token
]
199 if token
.type in handlers
and token
.varname
is not None:
200 args
= args
+ [token
.varname
]
201 for nexttkn
in token
.next():
202 get_always_args(nexttkn
, always_args
, args
, stack
)
205 def load(self
, filename
):
206 filedata
= clippy
.parse(filename
)
207 for entry
in filedata
['data']:
208 if entry
['type'] != 'PREPROC':
210 ppdir
= entry
['line'].lstrip().split(None, 1)
211 if ppdir
[0] != 'define' or len(ppdir
) != 2:
213 ppdef
= ppdir
[1].split(None, 1)
217 val
= ppdef
[1] if len(ppdef
) == 2 else ''
219 val
= val
.strip(' \t\n\\')
221 sys
.stderr
.write('warning: macro %s redefined!\n' % (name
))
224 def process_file(fn
, ofd
, dumpfd
, all_defun
, macros
):
226 filedata
= clippy
.parse(fn
)
228 for entry
in filedata
['data']:
229 if entry
['type'].startswith('DEFPY') or (all_defun
and entry
['type'].startswith('DEFUN')):
230 if len(entry
['args'][0]) != 1:
231 sys
.stderr
.write('%s:%d: DEFPY function name not parseable (%r)\n' % (fn
, entry
['lineno'], entry
['args'][0]))
235 cmddef
= entry
['args'][2]
240 if i
.startswith('"') and i
.endswith('"'):
241 cmddefx
.append(i
[1:-1])
244 sys
.stderr
.write('%s:%d: DEFPY command string not parseable (%r)\n' % (fn
, entry
['lineno'], cmddef
))
250 cmddef
= ''.join([i
for i
in cmddefx
])
252 graph
= clippy
.Graph(cmddef
)
255 for token
, depth
in clippy
.graph_iterate(graph
):
256 if token
.type not in handlers
:
258 if token
.varname
is None:
260 arg
= args
.setdefault(token
.varname
, [])
261 arg
.append(handlers
[token
.type](token
))
262 always_args
.add(token
.varname
)
264 get_always_args(graph
.first(), always_args
)
271 params
= { 'cmddef': cmddef
, 'fnname': entry
['args'][0][0] }
280 def do_add(handler
, basename
, varname
, attr
= ''):
281 argdefs
.append(',\\\n\t%s %s%s' % (handler
.argtype
, varname
, attr
))
282 argdecls
.append('\t%s\n' % (handler
.decl
.substitute({'varname': varname
}).replace('\n', '\n\t')))
283 arglist
.append(', %s%s' % (handler
.deref
, varname
))
284 if basename
in always_args
and handler
.canassert
:
285 argassert
.append('''\tif (!%s) {
286 \t\tvty_out(vty, "Internal CLI error [%%s]\\n", "%s");
287 \t\treturn CMD_WARNING;
288 \t}\n''' % (varname
, varname
))
291 if not at
.startswith('const '):
293 doc
.append('\t%-26s %s %s' % (at
, 'alw' if basename
in always_args
else 'opt', varname
))
295 for varname
in args
.keys():
296 handler
= mix_handlers(args
[varname
])
297 #print(varname, handler)
298 if handler
is None: continue
299 do_add(handler
, varname
, varname
)
300 code
= handler
.code
.substitute({'varname': varname
}).replace('\n', '\n\t\t\t')
304 if not handler
.drop_str
:
305 do_add(StringHandler(None), varname
, '%s_str' % (varname
), ' __attribute__ ((unused))')
306 strblock
= '\n\t\t\t%s_str = argv[_i]->arg;' % (varname
)
307 argblocks
.append(argblock
.substitute({'varname': varname
, 'strblock': strblock
, 'code': code
}))
309 if dumpfd
is not None:
311 dumpfd
.write('"%s":\n%s\n\n' % (cmddef
, '\n'.join(doc
)))
313 dumpfd
.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef
))
315 params
['argdefs'] = ''.join(argdefs
)
316 params
['argdecls'] = ''.join(argdecls
)
317 params
['arglist'] = ''.join(arglist
)
318 params
['argblocks'] = ''.join(argblocks
)
319 params
['canfail'] = canfail
320 params
['nonempty'] = len(argblocks
)
321 params
['argassert'] = ''.join(argassert
)
322 ofd
.write(templ
.substitute(params
))
326 if __name__
== '__main__':
329 argp
= argparse
.ArgumentParser(description
= 'FRR CLI preprocessor in Python')
330 argp
.add_argument('--all-defun', action
= 'store_const', const
= True,
331 help = 'process DEFUN() statements in addition to DEFPY()')
332 argp
.add_argument('--show', action
= 'store_const', const
= True,
333 help = 'print out list of arguments and types for each definition')
334 argp
.add_argument('-o', type = str, metavar
= 'OUTFILE',
335 help = 'output C file name')
336 argp
.add_argument('cfile', type = str)
337 args
= argp
.parse_args()
340 if args
.o
is not None:
350 macros
.load('lib/route_types.h')
351 macros
.load('lib/command.h')
353 macros
['PROTO_REDIST_STR'] = 'FRR_REDIST_STR_ISISD'
355 errors
= process_file(args
.cfile
, ofd
, dumpfd
, args
.all_defun
, macros
)
359 if args
.o
is not None:
360 clippy
.wrdiff(args
.o
, ofd
, [args
.cfile
, os
.path
.realpath(__file__
), sys
.executable
])