]>
git.proxmox.com Git - mirror_frr.git/blob - python/clidef.py
1 # SPDX-License-Identifier: GPL-2.0-or-later
2 # FRR CLI preprocessor (DEFPY)
4 # Copyright (C) 2017 David Lamparter for NetDEF, Inc.
6 import clippy
, traceback
, sys
, os
7 from collections
import OrderedDict
8 from functools
import reduce
9 from pprint
import pprint
10 from string
import Template
11 from io
import StringIO
13 # the various handlers generate output C code for a particular type of
14 # CLI token, choosing the most useful output C type.
17 class RenderHandler(object):
18 def __init__(self
, token
):
21 def combine(self
, other
):
22 if type(self
) == type(other
):
24 return StringHandler(None)
32 class StringHandler(RenderHandler
):
33 argtype
= "const char *"
34 decl
= Template("const char *$varname = NULL;")
36 "$varname = (argv[_i]->type == WORD_TKN) ? argv[_i]->text : argv[_i]->arg;"
43 class LongHandler(RenderHandler
):
45 decl
= Template("long $varname = 0;")
49 $varname = strtol(argv[_i]->arg, &_end, 10);
50 _fail = (_end == argv[_i]->arg) || (*_end != '\\0');"""
54 class AsDotHandler(RenderHandler
):
56 decl
= Template("as_t $varname = 0;")
57 code
= Template("_fail = !asn_str2asn(argv[_i]->arg, &$varname);")
60 # A.B.C.D/M (prefix_ipv4) and
61 # X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a
65 class PrefixBase(RenderHandler
):
66 def combine(self
, other
):
67 if type(self
) == type(other
):
69 if isinstance(other
, PrefixBase
):
70 return PrefixGenHandler(None)
71 return StringHandler(None)
76 class Prefix4Handler(PrefixBase
):
77 argtype
= "const struct prefix_ipv4 *"
78 decl
= Template("struct prefix_ipv4 $varname = { };")
79 code
= Template("_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);")
82 class Prefix6Handler(PrefixBase
):
83 argtype
= "const struct prefix_ipv6 *"
84 decl
= Template("struct prefix_ipv6 $varname = { };")
85 code
= Template("_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);")
88 class PrefixEthHandler(PrefixBase
):
89 argtype
= "struct prefix_eth *"
90 decl
= Template("struct prefix_eth $varname = { };")
91 code
= Template("_fail = !str2prefix_eth(argv[_i]->arg, &$varname);")
94 class PrefixGenHandler(PrefixBase
):
95 argtype
= "const struct prefix *"
96 decl
= Template("struct prefix $varname = { };")
97 code
= Template("_fail = !str2prefix(argv[_i]->arg, &$varname);")
100 # same for IP addresses. result is union sockunion.
101 class IPBase(RenderHandler
):
102 def combine(self
, other
):
103 if type(self
) == type(other
):
105 if type(other
) in [IP4Handler
, IP6Handler
, IPGenHandler
]:
106 return IPGenHandler(None)
107 return StringHandler(None)
110 class IP4Handler(IPBase
):
111 argtype
= "struct in_addr"
112 decl
= Template("struct in_addr $varname = { INADDR_ANY };")
113 code
= Template("_fail = !inet_aton(argv[_i]->arg, &$varname);")
116 class IP6Handler(IPBase
):
117 argtype
= "struct in6_addr"
118 decl
= Template("struct in6_addr $varname = {};")
119 code
= Template("_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);")
122 class IPGenHandler(IPBase
):
123 argtype
= "const union sockunion *"
125 """union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;"""
129 if (argv[_i]->text[0] == 'X') {
130 s__$varname.sa.sa_family = AF_INET6;
131 _fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr);
132 $varname = &s__$varname;
134 s__$varname.sa.sa_family = AF_INET;
135 _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
136 $varname = &s__$varname;
142 def mix_handlers(handlers
):
148 return reduce(combine
, handlers
, None)
152 "WORD_TKN": StringHandler
,
153 "VARIABLE_TKN": StringHandler
,
154 "RANGE_TKN": LongHandler
,
155 "IPV4_TKN": IP4Handler
,
156 "IPV4_PREFIX_TKN": Prefix4Handler
,
157 "IPV6_TKN": IP6Handler
,
158 "IPV6_PREFIX_TKN": Prefix6Handler
,
159 "MAC_TKN": PrefixEthHandler
,
160 "MAC_PREFIX_TKN": PrefixEthHandler
,
161 "ASNUM_TKN": AsDotHandler
,
164 # core template invoked for each occurence of DEFPY.
166 # the "#if $..." bits are there to keep this template unified into one
167 # common form, without requiring a more advanced template engine (e.g.
170 """$cond_begin/* $fnname => "$cmddef" */
171 DEFUN_CMD_FUNC_DECL($fnname)
172 #define funcdecl_$fnname static int ${fnname}_magic(\\
173 const struct cmd_element *self __attribute__ ((unused)),\\
174 struct vty *vty __attribute__ ((unused)),\\
175 int argc __attribute__ ((unused)),\\
176 struct cmd_token *argv[] __attribute__ ((unused))$argdefs)
178 DEFUN_CMD_FUNC_TEXT($fnname)
180 #if $nonempty /* anything to parse? */
182 #if $canfail /* anything that can fail? */
183 unsigned _fail = 0, _failcnt = 0;
186 for (_i = 0; _i < argc; _i++) {
187 if (!argv[_i]->varname)
189 #if $canfail /* anything that can fail? */
193 #if $canfail /* anything that can fail? */
195 vty_out (vty, "%% invalid input for %s: %s\\n",
196 argv[_i]->varname, argv[_i]->arg);
200 #if $canfail /* anything that can fail? */
206 return ${fnname}_magic(self, vty, argc, argv$arglist);
212 # invoked for each named parameter
215 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
221 def get_always_args(token
, always_args
, args
=[], stack
=[]):
224 if token
.type == "END_TKN":
225 for arg
in list(always_args
):
227 always_args
.remove(arg
)
230 stack
= stack
+ [token
]
231 if token
.type in handlers
and token
.varname
is not None:
232 args
= args
+ [token
.varname
]
233 for nexttkn
in token
.next():
234 get_always_args(nexttkn
, always_args
, args
, stack
)
242 def load(self
, filename
):
243 filedata
= clippy
.parse(filename
)
244 for entry
in filedata
["data"]:
245 if entry
["type"] != "PREPROC":
247 self
.load_preproc(filename
, entry
)
249 def setup(self
, key
, val
, where
="built-in"):
251 self
._loc
[key
] = (where
, 0)
253 def load_preproc(self
, filename
, entry
):
254 ppdir
= entry
["line"].lstrip().split(None, 1)
255 if ppdir
[0] != "define" or len(ppdir
) != 2:
257 ppdef
= ppdir
[1].split(None, 1)
261 val
= ppdef
[1] if len(ppdef
) == 2 else ""
263 val
= val
.strip(" \t\n\\")
264 if self
.get(name
, val
) != val
:
266 "%s:%d: warning: macro %s redefined!\n"
274 "%s:%d: note: previously defined here\n"
282 self
._loc
[name
] = (filename
, entry
["lineno"])
285 def process_file(fn
, ofd
, dumpfd
, all_defun
, macros
):
287 filedata
= clippy
.parse(fn
)
291 for entry
in filedata
["data"]:
292 if entry
["type"] == "PREPROC":
293 line
= entry
["line"].lstrip()
294 tokens
= line
.split(maxsplit
=1)
295 line
= "#" + line
+ "\n"
300 if tokens
[0] in ["if", "ifdef", "ifndef"]:
301 cond_stack
.append(line
)
302 elif tokens
[0] in ["elif", "else"]:
303 prev_line
= cond_stack
.pop(-1)
304 cond_stack
.append(prev_line
+ line
)
305 elif tokens
[0] in ["endif"]:
307 elif tokens
[0] in ["define"]:
309 macros
.load_preproc(fn
, entry
)
310 elif len(cond_stack
) == 1 and cond_stack
[0] == "#ifdef CLIPPY\n":
311 macros
.load_preproc(fn
, entry
)
313 if entry
["type"].startswith("DEFPY") or (
314 all_defun
and entry
["type"].startswith("DEFUN")
316 if len(entry
["args"][0]) != 1:
318 "%s:%d: DEFPY function name not parseable (%r)\n"
319 % (fn
, entry
["lineno"], entry
["args"][0])
324 cmddef
= entry
["args"][2]
329 if i
.startswith('"') and i
.endswith('"'):
330 cmddefx
.append(i
[1:-1])
334 "%s:%d: DEFPY command string not parseable (%r)\n"
335 % (fn
, entry
["lineno"], cmddef
)
342 cmddef
= "".join([i
for i
in cmddefx
])
344 graph
= clippy
.Graph(cmddef
)
347 for token
, depth
in clippy
.graph_iterate(graph
):
348 if token
.type not in handlers
:
350 if token
.varname
is None:
352 arg
= args
.setdefault(token
.varname
, [])
353 arg
.append(handlers
[token
.type](token
))
354 always_args
.add(token
.varname
)
356 get_always_args(graph
.first(), always_args
)
363 params
= {"cmddef": cmddef
, "fnname": entry
["args"][0][0]}
372 def do_add(handler
, basename
, varname
, attr
=""):
373 argdefs
.append(",\\\n\t%s %s%s" % (handler
.argtype
, varname
, attr
))
377 handler
.decl
.substitute({"varname": varname
}).replace(
382 arglist
.append(", %s%s" % (handler
.deref
, varname
))
383 if basename
in always_args
and handler
.canassert
:
386 \t\tvty_out(vty, "Internal CLI error [%%s]\\n", "%s");
387 \t\treturn CMD_WARNING;
393 if not at
.startswith("const "):
397 % (at
, "alw" if basename
in always_args
else "opt", varname
)
400 for varname
in args
.keys():
401 handler
= mix_handlers(args
[varname
])
402 # print(varname, handler)
405 do_add(handler
, varname
, varname
)
406 code
= handler
.code
.substitute({"varname": varname
}).replace(
412 if not handler
.drop_str
:
416 "%s_str" % (varname
),
417 " __attribute__ ((unused))",
419 strblock
= "\n\t\t\t%s_str = argv[_i]->arg;" % (varname
)
422 {"varname": varname
, "strblock": strblock
, "code": code
}
426 if dumpfd
is not None:
428 dumpfd
.write('"%s":\n%s\n\n' % (cmddef
, "\n".join(doc
)))
430 dumpfd
.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef
))
432 params
["cond_begin"] = "".join(cond_stack
)
433 params
["cond_end"] = "".join(["#endif\n"] * len(cond_stack
))
434 params
["argdefs"] = "".join(argdefs
)
435 params
["argdecls"] = "".join(argdecls
)
436 params
["arglist"] = "".join(arglist
)
437 params
["argblocks"] = "".join(argblocks
)
438 params
["canfail"] = canfail
439 params
["nonempty"] = len(argblocks
)
440 params
["argassert"] = "".join(argassert
)
441 ofd
.write(templ
.substitute(params
))
446 if __name__
== "__main__":
449 argp
= argparse
.ArgumentParser(description
="FRR CLI preprocessor in Python")
452 action
="store_const",
454 help="process DEFUN() statements in addition to DEFPY()",
458 action
="store_const",
460 help="print out list of arguments and types for each definition",
462 argp
.add_argument("-o", type=str, metavar
="OUTFILE", help="output C file name")
463 argp
.add_argument("cfile", type=str)
464 args
= argp
.parse_args()
467 if args
.o
is not None:
476 basepath
= os
.path
.dirname(os
.path
.dirname(os
.path
.abspath(__file__
)))
479 macros
.load("lib/route_types.h")
480 macros
.load(os
.path
.join(basepath
, "lib/command.h"))
481 macros
.load(os
.path
.join(basepath
, "bgpd/bgp_vty.h"))
483 macros
.setup("PROTO_REDIST_STR", "FRR_REDIST_STR_ISISD")
484 macros
.setup("PROTO_IP_REDIST_STR", "FRR_IP_REDIST_STR_ISISD")
485 macros
.setup("PROTO_IP6_REDIST_STR", "FRR_IP6_REDIST_STR_ISISD")
487 errors
= process_file(args
.cfile
, ofd
, dumpfd
, args
.all_defun
, macros
)
491 if args
.o
is not None:
493 args
.o
, ofd
, [args
.cfile
, os
.path
.realpath(__file__
), sys
.executable
]