]>
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 """$cond_begin/* $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
)
248 def load(self
, filename
):
249 filedata
= clippy
.parse(filename
)
250 for entry
in filedata
["data"]:
251 if entry
["type"] != "PREPROC":
253 self
.load_preproc(filename
, entry
)
255 def setup(self
, key
, val
, where
="built-in"):
257 self
._loc
[key
] = (where
, 0)
259 def load_preproc(self
, filename
, entry
):
260 ppdir
= entry
["line"].lstrip().split(None, 1)
261 if ppdir
[0] != "define" or len(ppdir
) != 2:
263 ppdef
= ppdir
[1].split(None, 1)
267 val
= ppdef
[1] if len(ppdef
) == 2 else ""
269 val
= val
.strip(" \t\n\\")
270 if self
.get(name
, val
) != val
:
272 "%s:%d: warning: macro %s redefined!\n"
280 "%s:%d: note: previously defined here\n"
288 self
._loc
[name
] = (filename
, entry
["lineno"])
291 def process_file(fn
, ofd
, dumpfd
, all_defun
, macros
):
293 filedata
= clippy
.parse(fn
)
297 for entry
in filedata
["data"]:
298 if entry
["type"] == "PREPROC":
299 line
= entry
["line"].lstrip()
300 tokens
= line
.split(maxsplit
=1)
301 line
= "#" + line
+ "\n"
306 if tokens
[0] in ["if", "ifdef", "ifndef"]:
307 cond_stack
.append(line
)
308 elif tokens
[0] in ["elif", "else"]:
309 prev_line
= cond_stack
.pop(-1)
310 cond_stack
.append(prev_line
+ line
)
311 elif tokens
[0] in ["endif"]:
313 elif tokens
[0] in ["define"]:
315 macros
.load_preproc(fn
, entry
)
316 elif len(cond_stack
) == 1 and cond_stack
[0] == "#ifdef CLIPPY\n":
317 macros
.load_preproc(fn
, entry
)
319 if entry
["type"].startswith("DEFPY") or (
320 all_defun
and entry
["type"].startswith("DEFUN")
322 if len(entry
["args"][0]) != 1:
324 "%s:%d: DEFPY function name not parseable (%r)\n"
325 % (fn
, entry
["lineno"], entry
["args"][0])
330 cmddef
= entry
["args"][2]
335 if i
.startswith('"') and i
.endswith('"'):
336 cmddefx
.append(i
[1:-1])
340 "%s:%d: DEFPY command string not parseable (%r)\n"
341 % (fn
, entry
["lineno"], cmddef
)
348 cmddef
= "".join([i
for i
in cmddefx
])
350 graph
= clippy
.Graph(cmddef
)
353 for token
, depth
in clippy
.graph_iterate(graph
):
354 if token
.type not in handlers
:
356 if token
.varname
is None:
358 arg
= args
.setdefault(token
.varname
, [])
359 arg
.append(handlers
[token
.type](token
))
360 always_args
.add(token
.varname
)
362 get_always_args(graph
.first(), always_args
)
369 params
= {"cmddef": cmddef
, "fnname": entry
["args"][0][0]}
378 def do_add(handler
, basename
, varname
, attr
=""):
379 argdefs
.append(",\\\n\t%s %s%s" % (handler
.argtype
, varname
, attr
))
383 handler
.decl
.substitute({"varname": varname
}).replace(
388 arglist
.append(", %s%s" % (handler
.deref
, varname
))
389 if basename
in always_args
and handler
.canassert
:
392 \t\tvty_out(vty, "Internal CLI error [%%s]\\n", "%s");
393 \t\treturn CMD_WARNING;
399 if not at
.startswith("const "):
403 % (at
, "alw" if basename
in always_args
else "opt", varname
)
406 for varname
in args
.keys():
407 handler
= mix_handlers(args
[varname
])
408 # print(varname, handler)
411 do_add(handler
, varname
, varname
)
412 code
= handler
.code
.substitute({"varname": varname
}).replace(
418 if not handler
.drop_str
:
422 "%s_str" % (varname
),
423 " __attribute__ ((unused))",
425 strblock
= "\n\t\t\t%s_str = argv[_i]->arg;" % (varname
)
428 {"varname": varname
, "strblock": strblock
, "code": code
}
432 if dumpfd
is not None:
434 dumpfd
.write('"%s":\n%s\n\n' % (cmddef
, "\n".join(doc
)))
436 dumpfd
.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef
))
438 params
["cond_begin"] = "".join(cond_stack
)
439 params
["cond_end"] = "".join(["#endif\n"] * len(cond_stack
))
440 params
["argdefs"] = "".join(argdefs
)
441 params
["argdecls"] = "".join(argdecls
)
442 params
["arglist"] = "".join(arglist
)
443 params
["argblocks"] = "".join(argblocks
)
444 params
["canfail"] = canfail
445 params
["nonempty"] = len(argblocks
)
446 params
["argassert"] = "".join(argassert
)
447 ofd
.write(templ
.substitute(params
))
452 if __name__
== "__main__":
455 argp
= argparse
.ArgumentParser(description
="FRR CLI preprocessor in Python")
458 action
="store_const",
460 help="process DEFUN() statements in addition to DEFPY()",
464 action
="store_const",
466 help="print out list of arguments and types for each definition",
468 argp
.add_argument("-o", type=str, metavar
="OUTFILE", help="output C file name")
469 argp
.add_argument("cfile", type=str)
470 args
= argp
.parse_args()
473 if args
.o
is not None:
482 basepath
= os
.path
.dirname(os
.path
.dirname(os
.path
.abspath(__file__
)))
485 macros
.load("lib/route_types.h")
486 macros
.load(os
.path
.join(basepath
, "lib/command.h"))
487 macros
.load(os
.path
.join(basepath
, "bgpd/bgp_vty.h"))
489 macros
.setup("PROTO_REDIST_STR", "FRR_REDIST_STR_ISISD")
490 macros
.setup("PROTO_IP_REDIST_STR", "FRR_IP_REDIST_STR_ISISD")
491 macros
.setup("PROTO_IP6_REDIST_STR", "FRR_IP6_REDIST_STR_ISISD")
493 errors
= process_file(args
.cfile
, ofd
, dumpfd
, args
.all_defun
, macros
)
497 if args
.o
is not None:
499 args
.o
, ofd
, [args
.cfile
, os
.path
.realpath(__file__
), sys
.executable
]