]>
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 # A.B.C.D/M (prefix_ipv4) and
55 # X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a
59 class PrefixBase(RenderHandler
):
60 def combine(self
, other
):
61 if type(self
) == type(other
):
63 if isinstance(other
, PrefixBase
):
64 return PrefixGenHandler(None)
65 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);")
76 class Prefix6Handler(PrefixBase
):
77 argtype
= "const struct prefix_ipv6 *"
78 decl
= Template("struct prefix_ipv6 $varname = { };")
79 code
= Template("_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);")
82 class PrefixEthHandler(PrefixBase
):
83 argtype
= "struct prefix_eth *"
84 decl
= Template("struct prefix_eth $varname = { };")
85 code
= Template("_fail = !str2prefix_eth(argv[_i]->arg, &$varname);")
88 class PrefixGenHandler(PrefixBase
):
89 argtype
= "const struct prefix *"
90 decl
= Template("struct prefix $varname = { };")
91 code
= Template("_fail = !str2prefix(argv[_i]->arg, &$varname);")
94 # same for IP addresses. result is union sockunion.
95 class IPBase(RenderHandler
):
96 def combine(self
, other
):
97 if type(self
) == type(other
):
99 if type(other
) in [IP4Handler
, IP6Handler
, IPGenHandler
]:
100 return IPGenHandler(None)
101 return StringHandler(None)
104 class IP4Handler(IPBase
):
105 argtype
= "struct in_addr"
106 decl
= Template("struct in_addr $varname = { INADDR_ANY };")
107 code
= Template("_fail = !inet_aton(argv[_i]->arg, &$varname);")
110 class IP6Handler(IPBase
):
111 argtype
= "struct in6_addr"
112 decl
= Template("struct in6_addr $varname = {};")
113 code
= Template("_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);")
116 class IPGenHandler(IPBase
):
117 argtype
= "const union sockunion *"
119 """union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;"""
123 if (argv[_i]->text[0] == 'X') {
124 s__$varname.sa.sa_family = AF_INET6;
125 _fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr);
126 $varname = &s__$varname;
128 s__$varname.sa.sa_family = AF_INET;
129 _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
130 $varname = &s__$varname;
136 def mix_handlers(handlers
):
142 return reduce(combine
, handlers
, None)
146 "WORD_TKN": StringHandler
,
147 "VARIABLE_TKN": StringHandler
,
148 "RANGE_TKN": LongHandler
,
149 "IPV4_TKN": IP4Handler
,
150 "IPV4_PREFIX_TKN": Prefix4Handler
,
151 "IPV6_TKN": IP6Handler
,
152 "IPV6_PREFIX_TKN": Prefix6Handler
,
153 "MAC_TKN": PrefixEthHandler
,
154 "MAC_PREFIX_TKN": PrefixEthHandler
,
157 # core template invoked for each occurence of DEFPY.
159 # the "#if $..." bits are there to keep this template unified into one
160 # common form, without requiring a more advanced template engine (e.g.
163 """$cond_begin/* $fnname => "$cmddef" */
164 DEFUN_CMD_FUNC_DECL($fnname)
165 #define funcdecl_$fnname static int ${fnname}_magic(\\
166 const struct cmd_element *self __attribute__ ((unused)),\\
167 struct vty *vty __attribute__ ((unused)),\\
168 int argc __attribute__ ((unused)),\\
169 struct cmd_token *argv[] __attribute__ ((unused))$argdefs)
171 DEFUN_CMD_FUNC_TEXT($fnname)
173 #if $nonempty /* anything to parse? */
175 #if $canfail /* anything that can fail? */
176 unsigned _fail = 0, _failcnt = 0;
179 for (_i = 0; _i < argc; _i++) {
180 if (!argv[_i]->varname)
182 #if $canfail /* anything that can fail? */
186 #if $canfail /* anything that can fail? */
188 vty_out (vty, "%% invalid input for %s: %s\\n",
189 argv[_i]->varname, argv[_i]->arg);
193 #if $canfail /* anything that can fail? */
199 return ${fnname}_magic(self, vty, argc, argv$arglist);
205 # invoked for each named parameter
208 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
214 def get_always_args(token
, always_args
, args
=[], stack
=[]):
217 if token
.type == "END_TKN":
218 for arg
in list(always_args
):
220 always_args
.remove(arg
)
223 stack
= stack
+ [token
]
224 if token
.type in handlers
and token
.varname
is not None:
225 args
= args
+ [token
.varname
]
226 for nexttkn
in token
.next():
227 get_always_args(nexttkn
, always_args
, args
, stack
)
235 def load(self
, filename
):
236 filedata
= clippy
.parse(filename
)
237 for entry
in filedata
["data"]:
238 if entry
["type"] != "PREPROC":
240 self
.load_preproc(filename
, entry
)
242 def setup(self
, key
, val
, where
="built-in"):
244 self
._loc
[key
] = (where
, 0)
246 def load_preproc(self
, filename
, entry
):
247 ppdir
= entry
["line"].lstrip().split(None, 1)
248 if ppdir
[0] != "define" or len(ppdir
) != 2:
250 ppdef
= ppdir
[1].split(None, 1)
254 val
= ppdef
[1] if len(ppdef
) == 2 else ""
256 val
= val
.strip(" \t\n\\")
257 if self
.get(name
, val
) != val
:
259 "%s:%d: warning: macro %s redefined!\n"
267 "%s:%d: note: previously defined here\n"
275 self
._loc
[name
] = (filename
, entry
["lineno"])
278 def process_file(fn
, ofd
, dumpfd
, all_defun
, macros
):
280 filedata
= clippy
.parse(fn
)
284 for entry
in filedata
["data"]:
285 if entry
["type"] == "PREPROC":
286 line
= entry
["line"].lstrip()
287 tokens
= line
.split(maxsplit
=1)
288 line
= "#" + line
+ "\n"
293 if tokens
[0] in ["if", "ifdef", "ifndef"]:
294 cond_stack
.append(line
)
295 elif tokens
[0] in ["elif", "else"]:
296 prev_line
= cond_stack
.pop(-1)
297 cond_stack
.append(prev_line
+ line
)
298 elif tokens
[0] in ["endif"]:
300 elif tokens
[0] in ["define"]:
302 macros
.load_preproc(fn
, entry
)
303 elif len(cond_stack
) == 1 and cond_stack
[0] == "#ifdef CLIPPY\n":
304 macros
.load_preproc(fn
, entry
)
306 if entry
["type"].startswith("DEFPY") or (
307 all_defun
and entry
["type"].startswith("DEFUN")
309 if len(entry
["args"][0]) != 1:
311 "%s:%d: DEFPY function name not parseable (%r)\n"
312 % (fn
, entry
["lineno"], entry
["args"][0])
317 cmddef
= entry
["args"][2]
322 if i
.startswith('"') and i
.endswith('"'):
323 cmddefx
.append(i
[1:-1])
327 "%s:%d: DEFPY command string not parseable (%r)\n"
328 % (fn
, entry
["lineno"], cmddef
)
335 cmddef
= "".join([i
for i
in cmddefx
])
337 graph
= clippy
.Graph(cmddef
)
340 for token
, depth
in clippy
.graph_iterate(graph
):
341 if token
.type not in handlers
:
343 if token
.varname
is None:
345 arg
= args
.setdefault(token
.varname
, [])
346 arg
.append(handlers
[token
.type](token
))
347 always_args
.add(token
.varname
)
349 get_always_args(graph
.first(), always_args
)
356 params
= {"cmddef": cmddef
, "fnname": entry
["args"][0][0]}
365 def do_add(handler
, basename
, varname
, attr
=""):
366 argdefs
.append(",\\\n\t%s %s%s" % (handler
.argtype
, varname
, attr
))
370 handler
.decl
.substitute({"varname": varname
}).replace(
375 arglist
.append(", %s%s" % (handler
.deref
, varname
))
376 if basename
in always_args
and handler
.canassert
:
379 \t\tvty_out(vty, "Internal CLI error [%%s]\\n", "%s");
380 \t\treturn CMD_WARNING;
386 if not at
.startswith("const "):
390 % (at
, "alw" if basename
in always_args
else "opt", varname
)
393 for varname
in args
.keys():
394 handler
= mix_handlers(args
[varname
])
395 # print(varname, handler)
398 do_add(handler
, varname
, varname
)
399 code
= handler
.code
.substitute({"varname": varname
}).replace(
405 if not handler
.drop_str
:
409 "%s_str" % (varname
),
410 " __attribute__ ((unused))",
412 strblock
= "\n\t\t\t%s_str = argv[_i]->arg;" % (varname
)
415 {"varname": varname
, "strblock": strblock
, "code": code
}
419 if dumpfd
is not None:
421 dumpfd
.write('"%s":\n%s\n\n' % (cmddef
, "\n".join(doc
)))
423 dumpfd
.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef
))
425 params
["cond_begin"] = "".join(cond_stack
)
426 params
["cond_end"] = "".join(["#endif\n"] * len(cond_stack
))
427 params
["argdefs"] = "".join(argdefs
)
428 params
["argdecls"] = "".join(argdecls
)
429 params
["arglist"] = "".join(arglist
)
430 params
["argblocks"] = "".join(argblocks
)
431 params
["canfail"] = canfail
432 params
["nonempty"] = len(argblocks
)
433 params
["argassert"] = "".join(argassert
)
434 ofd
.write(templ
.substitute(params
))
439 if __name__
== "__main__":
442 argp
= argparse
.ArgumentParser(description
="FRR CLI preprocessor in Python")
445 action
="store_const",
447 help="process DEFUN() statements in addition to DEFPY()",
451 action
="store_const",
453 help="print out list of arguments and types for each definition",
455 argp
.add_argument("-o", type=str, metavar
="OUTFILE", help="output C file name")
456 argp
.add_argument("cfile", type=str)
457 args
= argp
.parse_args()
460 if args
.o
is not None:
469 basepath
= os
.path
.dirname(os
.path
.dirname(os
.path
.abspath(__file__
)))
472 macros
.load("lib/route_types.h")
473 macros
.load(os
.path
.join(basepath
, "lib/command.h"))
474 macros
.load(os
.path
.join(basepath
, "bgpd/bgp_vty.h"))
476 macros
.setup("PROTO_REDIST_STR", "FRR_REDIST_STR_ISISD")
477 macros
.setup("PROTO_IP_REDIST_STR", "FRR_IP_REDIST_STR_ISISD")
478 macros
.setup("PROTO_IP6_REDIST_STR", "FRR_IP6_REDIST_STR_ISISD")
480 errors
= process_file(args
.cfile
, ofd
, dumpfd
, args
.all_defun
, macros
)
484 if args
.o
is not None:
486 args
.o
, ofd
, [args
.cfile
, os
.path
.realpath(__file__
), sys
.executable
]