]>
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.
30 class RenderHandler(object):
31 def __init__(self
, token
):
34 def combine(self
, other
):
35 if type(self
) == type(other
):
37 return StringHandler(None)
45 class StringHandler(RenderHandler
):
46 argtype
= "const char *"
47 decl
= Template("const char *$varname = NULL;")
49 "$varname = (argv[_i]->type == WORD_TKN) ? argv[_i]->text : argv[_i]->arg;"
56 class LongHandler(RenderHandler
):
58 decl
= Template("long $varname = 0;")
62 $varname = strtol(argv[_i]->arg, &_end, 10);
63 _fail = (_end == argv[_i]->arg) || (*_end != '\\0');"""
67 # A.B.C.D/M (prefix_ipv4) and
68 # X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a
72 class PrefixBase(RenderHandler
):
73 def combine(self
, other
):
74 if type(self
) == type(other
):
76 if isinstance(other
, PrefixBase
):
77 return PrefixGenHandler(None)
78 return StringHandler(None)
83 class Prefix4Handler(PrefixBase
):
84 argtype
= "const struct prefix_ipv4 *"
85 decl
= Template("struct prefix_ipv4 $varname = { };")
86 code
= Template("_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);")
89 class Prefix6Handler(PrefixBase
):
90 argtype
= "const struct prefix_ipv6 *"
91 decl
= Template("struct prefix_ipv6 $varname = { };")
92 code
= Template("_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);")
95 class PrefixEthHandler(PrefixBase
):
96 argtype
= "struct prefix_eth *"
97 decl
= Template("struct prefix_eth $varname = { };")
98 code
= Template("_fail = !str2prefix_eth(argv[_i]->arg, &$varname);")
101 class PrefixGenHandler(PrefixBase
):
102 argtype
= "const struct prefix *"
103 decl
= Template("struct prefix $varname = { };")
104 code
= Template("_fail = !str2prefix(argv[_i]->arg, &$varname);")
107 # same for IP addresses. result is union sockunion.
108 class IPBase(RenderHandler
):
109 def combine(self
, other
):
110 if type(self
) == type(other
):
112 if type(other
) in [IP4Handler
, IP6Handler
, IPGenHandler
]:
113 return IPGenHandler(None)
114 return StringHandler(None)
117 class IP4Handler(IPBase
):
118 argtype
= "struct in_addr"
119 decl
= Template("struct in_addr $varname = { INADDR_ANY };")
120 code
= Template("_fail = !inet_aton(argv[_i]->arg, &$varname);")
123 class IP6Handler(IPBase
):
124 argtype
= "struct in6_addr"
125 decl
= Template("struct in6_addr $varname = {};")
126 code
= Template("_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);")
129 class IPGenHandler(IPBase
):
130 argtype
= "const union sockunion *"
132 """union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;"""
136 if (argv[_i]->text[0] == 'X') {
137 s__$varname.sa.sa_family = AF_INET6;
138 _fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr);
139 $varname = &s__$varname;
141 s__$varname.sa.sa_family = AF_INET;
142 _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
143 $varname = &s__$varname;
149 def mix_handlers(handlers
):
155 return reduce(combine
, handlers
, None)
159 "WORD_TKN": StringHandler
,
160 "VARIABLE_TKN": StringHandler
,
161 "RANGE_TKN": LongHandler
,
162 "IPV4_TKN": IP4Handler
,
163 "IPV4_PREFIX_TKN": Prefix4Handler
,
164 "IPV6_TKN": IP6Handler
,
165 "IPV6_PREFIX_TKN": Prefix6Handler
,
166 "MAC_TKN": PrefixEthHandler
,
167 "MAC_PREFIX_TKN": PrefixEthHandler
,
170 # core template invoked for each occurence of DEFPY.
172 # the "#if $..." bits are there to keep this template unified into one
173 # common form, without requiring a more advanced template engine (e.g.
176 """/* $fnname => "$cmddef" */
177 DEFUN_CMD_FUNC_DECL($fnname)
178 #define funcdecl_$fnname static int ${fnname}_magic(\\
179 const struct cmd_element *self __attribute__ ((unused)),\\
180 struct vty *vty __attribute__ ((unused)),\\
181 int argc __attribute__ ((unused)),\\
182 struct cmd_token *argv[] __attribute__ ((unused))$argdefs)
184 DEFUN_CMD_FUNC_TEXT($fnname)
186 #if $nonempty /* anything to parse? */
188 #if $canfail /* anything that can fail? */
189 unsigned _fail = 0, _failcnt = 0;
192 for (_i = 0; _i < argc; _i++) {
193 if (!argv[_i]->varname)
195 #if $canfail /* anything that can fail? */
199 #if $canfail /* anything that can fail? */
201 vty_out (vty, "%% invalid input for %s: %s\\n",
202 argv[_i]->varname, argv[_i]->arg);
206 #if $canfail /* anything that can fail? */
212 return ${fnname}_magic(self, vty, argc, argv$arglist);
218 # invoked for each named parameter
221 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
227 def get_always_args(token
, always_args
, args
=[], stack
=[]):
230 if token
.type == "END_TKN":
231 for arg
in list(always_args
):
233 always_args
.remove(arg
)
236 stack
= stack
+ [token
]
237 if token
.type in handlers
and token
.varname
is not None:
238 args
= args
+ [token
.varname
]
239 for nexttkn
in token
.next():
240 get_always_args(nexttkn
, always_args
, args
, stack
)
244 def load(self
, filename
):
245 filedata
= clippy
.parse(filename
)
246 for entry
in filedata
["data"]:
247 if entry
["type"] != "PREPROC":
249 ppdir
= entry
["line"].lstrip().split(None, 1)
250 if ppdir
[0] != "define" or len(ppdir
) != 2:
252 ppdef
= ppdir
[1].split(None, 1)
256 val
= ppdef
[1] if len(ppdef
) == 2 else ""
258 val
= val
.strip(" \t\n\\")
260 sys
.stderr
.write("warning: macro %s redefined!\n" % (name
))
264 def process_file(fn
, ofd
, dumpfd
, all_defun
, macros
):
266 filedata
= clippy
.parse(fn
)
268 for entry
in filedata
["data"]:
269 if entry
["type"].startswith("DEFPY") or (
270 all_defun
and entry
["type"].startswith("DEFUN")
272 if len(entry
["args"][0]) != 1:
274 "%s:%d: DEFPY function name not parseable (%r)\n"
275 % (fn
, entry
["lineno"], entry
["args"][0])
280 cmddef
= entry
["args"][2]
285 if i
.startswith('"') and i
.endswith('"'):
286 cmddefx
.append(i
[1:-1])
290 "%s:%d: DEFPY command string not parseable (%r)\n"
291 % (fn
, entry
["lineno"], cmddef
)
298 cmddef
= "".join([i
for i
in cmddefx
])
300 graph
= clippy
.Graph(cmddef
)
303 for token
, depth
in clippy
.graph_iterate(graph
):
304 if token
.type not in handlers
:
306 if token
.varname
is None:
308 arg
= args
.setdefault(token
.varname
, [])
309 arg
.append(handlers
[token
.type](token
))
310 always_args
.add(token
.varname
)
312 get_always_args(graph
.first(), always_args
)
319 params
= {"cmddef": cmddef
, "fnname": entry
["args"][0][0]}
328 def do_add(handler
, basename
, varname
, attr
=""):
329 argdefs
.append(",\\\n\t%s %s%s" % (handler
.argtype
, varname
, attr
))
333 handler
.decl
.substitute({"varname": varname
}).replace(
338 arglist
.append(", %s%s" % (handler
.deref
, varname
))
339 if basename
in always_args
and handler
.canassert
:
342 \t\tvty_out(vty, "Internal CLI error [%%s]\\n", "%s");
343 \t\treturn CMD_WARNING;
349 if not at
.startswith("const "):
353 % (at
, "alw" if basename
in always_args
else "opt", varname
)
356 for varname
in args
.keys():
357 handler
= mix_handlers(args
[varname
])
358 # print(varname, handler)
361 do_add(handler
, varname
, varname
)
362 code
= handler
.code
.substitute({"varname": varname
}).replace(
368 if not handler
.drop_str
:
372 "%s_str" % (varname
),
373 " __attribute__ ((unused))",
375 strblock
= "\n\t\t\t%s_str = argv[_i]->arg;" % (varname
)
378 {"varname": varname
, "strblock": strblock
, "code": code
}
382 if dumpfd
is not None:
384 dumpfd
.write('"%s":\n%s\n\n' % (cmddef
, "\n".join(doc
)))
386 dumpfd
.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef
))
388 params
["argdefs"] = "".join(argdefs
)
389 params
["argdecls"] = "".join(argdecls
)
390 params
["arglist"] = "".join(arglist
)
391 params
["argblocks"] = "".join(argblocks
)
392 params
["canfail"] = canfail
393 params
["nonempty"] = len(argblocks
)
394 params
["argassert"] = "".join(argassert
)
395 ofd
.write(templ
.substitute(params
))
400 if __name__
== "__main__":
403 argp
= argparse
.ArgumentParser(description
="FRR CLI preprocessor in Python")
406 action
="store_const",
408 help="process DEFUN() statements in addition to DEFPY()",
412 action
="store_const",
414 help="print out list of arguments and types for each definition",
416 argp
.add_argument("-o", type=str, metavar
="OUTFILE", help="output C file name")
417 argp
.add_argument("cfile", type=str)
418 args
= argp
.parse_args()
421 if args
.o
is not None:
430 basepath
= os
.path
.dirname(os
.path
.dirname(os
.path
.abspath(__file__
)))
433 macros
.load("lib/route_types.h")
434 macros
.load(os
.path
.join(basepath
, "lib/command.h"))
435 macros
.load(os
.path
.join(basepath
, "bgpd/bgp_vty.h"))
437 macros
["PROTO_REDIST_STR"] = "FRR_REDIST_STR_ISISD"
438 macros
["PROTO_IP_REDIST_STR"] = "FRR_IP_REDIST_STR_ISISD"
439 macros
["PROTO_IP6_REDIST_STR"] = "FRR_IP6_REDIST_STR_ISISD"
441 errors
= process_file(args
.cfile
, ofd
, dumpfd
, args
.all_defun
, macros
)
445 if args
.o
is not None:
447 args
.o
, ofd
, [args
.cfile
, os
.path
.realpath(__file__
), sys
.executable
]