mirror of
https://github.com/FRRouting/frr.git
synced 2025-04-30 13:37:17 +02:00
lib: clippy the assistant
Wraps the command parsing code for Python, so we can use it to do fancy preprocessing and replace extract.pl. Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
This commit is contained in:
parent
92e5026146
commit
29ad6f6882
102
configure.ac
102
configure.ac
|
@ -23,7 +23,36 @@ dnl Get hostname and other information.
|
|||
dnl -----------------------------------
|
||||
AC_CANONICAL_BUILD()
|
||||
AC_CANONICAL_HOST()
|
||||
AC_CANONICAL_TARGET()
|
||||
|
||||
AS_IF([test "$host" != "$build"], [
|
||||
if test "$srcdir" = "."; then
|
||||
AC_MSG_ERROR([cross-compilation is only possible with builddir separate from srcdir. create a separate directory and run as .../path-to-frr/configure.])
|
||||
fi
|
||||
test -d hosttools || mkdir hosttools
|
||||
abssrc="`cd \"${srcdir}\"; pwd`"
|
||||
|
||||
AC_MSG_NOTICE([...])
|
||||
AC_MSG_NOTICE([... cross-compilation: creating hosttools directory and self-configuring for build platform tools])
|
||||
AC_MSG_NOTICE([... use HOST_CPPFLAGS / HOST_CFLAGS / HOST_LDFLAGS if neccessary])
|
||||
AC_MSG_NOTICE([...])
|
||||
|
||||
( CPPFLAGS="$HOST_CPPFLAGS"; \
|
||||
CFLAGS="$HOST_CFLAGS"; \
|
||||
LDFLAGS="$HOST_LDFLAGS"; \
|
||||
cd hosttools; "${abssrc}/configure" "--host=$build" "--build=$build"; )
|
||||
|
||||
AC_MSG_NOTICE([...])
|
||||
AC_MSG_NOTICE([... cross-compilation: finished self-configuring for build platform tools])
|
||||
AC_MSG_NOTICE([...])
|
||||
|
||||
build_clippy="false"
|
||||
CLIPPYDIR="hosttools/lib"
|
||||
], [
|
||||
build_clippy="true"
|
||||
CLIPPYDIR="lib"
|
||||
])
|
||||
AC_SUBST(CLIPPYDIR)
|
||||
AM_CONDITIONAL([BUILD_CLIPPY], [$build_clippy])
|
||||
|
||||
# Disable portability warnings -- our automake code (in particular
|
||||
# common.am) uses some constructs specific to gmake.
|
||||
|
@ -431,6 +460,77 @@ if test "x${enable_dev_build}" = "xyes"; then
|
|||
fi
|
||||
AM_CONDITIONAL([DEV_BUILD], [test "x$enable_dev_build" = "xyes"])
|
||||
|
||||
#
|
||||
# Python for clippy
|
||||
#
|
||||
AS_IF([test "$host" = "$build"], [
|
||||
PYTHONCONFIG=""
|
||||
|
||||
# ordering:
|
||||
# 1. try python3, but respect the user's preference on which minor ver
|
||||
# 2. try python, which might be py3 or py2 again on the user's preference
|
||||
# 3. try python2 (can really only be 2.7 but eh)
|
||||
# 4. try 3.5 > 3.4 > 3.3 > 3.2 > 2.7 through pkg-config (no user pref)
|
||||
#
|
||||
# (AX_PYTHON_DEVEL has no clue about py3 vs py2)
|
||||
# (AX_PYTHON does not do what we need)
|
||||
|
||||
AC_CHECK_TOOLS([PYTHONCONFIG], [python3-config python-config python2-config])
|
||||
if test -n "$PYTHONCONFIG"; then
|
||||
PYTHON_CFLAGS="`\"${PYTHONCONFIG}\" --includes`"
|
||||
PYTHON_LIBS="`\"${PYTHONCONFIG}\" --libs`"
|
||||
|
||||
AC_MSG_CHECKING([whether we found a working Python version])
|
||||
AC_LINK_IFELSE_FLAGS([$PYTHON_CFLAGS], [$PYTHON_LIBS], [AC_LANG_PROGRAM([
|
||||
#include <Python.h>
|
||||
#if PY_VERSION_HEX < 0x02070000
|
||||
#error python too old
|
||||
#endif
|
||||
int main(void);
|
||||
],
|
||||
[
|
||||
{
|
||||
Py_Initialize();
|
||||
return 0;
|
||||
}
|
||||
])], [
|
||||
PYTHONCONFIG=""
|
||||
unset PYTHON_LIBS
|
||||
unset PYTHON_CFLAGS
|
||||
])
|
||||
fi
|
||||
|
||||
if test -z "$PYTHONCONFIG"; then
|
||||
PKG_CHECK_MODULES([PYTHON], python-3.5, [], [
|
||||
PKG_CHECK_MODULES([PYTHON], python-3.4, [], [
|
||||
PKG_CHECK_MODULES([PYTHON], python-3.3, [], [
|
||||
PKG_CHECK_MODULES([PYTHON], python-3.2, [], [
|
||||
PKG_CHECK_MODULES([PYTHON], python-2.7, [], [
|
||||
AC_MSG_FAILURE([could not find python-config or pkg-config python, please install Python development files from libpython-dev or similar])
|
||||
])])])])])
|
||||
|
||||
|
||||
AC_MSG_CHECKING([whether we found a working Python version])
|
||||
AC_LINK_IFELSE_FLAGS([$PYTHON_CFLAGS], [$PYTHON_LIBS], [AC_LANG_PROGRAM([
|
||||
#include <Python.h>
|
||||
#if PY_VERSION_HEX < 0x02070000
|
||||
#error python too old
|
||||
#endif
|
||||
int main(void);
|
||||
],
|
||||
[
|
||||
{
|
||||
Py_Initialize();
|
||||
return 0;
|
||||
}
|
||||
])], [
|
||||
AC_MSG_FAILURE([could not find python-config or pkg-config python, please install Python development files from libpython-dev or similar])
|
||||
])
|
||||
fi
|
||||
])
|
||||
AC_SUBST(PYTHON_CFLAGS)
|
||||
AC_SUBST(PYTHON_LIBS)
|
||||
|
||||
#
|
||||
# Logic for protobuf support.
|
||||
#
|
||||
|
|
|
@ -9,6 +9,7 @@ command_lex.h: command_lex.c
|
|||
@if test ! -f $@; then rm -f command_lex.c; else :; fi
|
||||
@if test ! -f $@; then $(MAKE) $(AM_MAKEFLAGS) command_lex.c; else :; fi
|
||||
command_parse.lo: command_lex.h
|
||||
clippy-command_parse.$(OBJEXT): command_lex.h
|
||||
|
||||
lib_LTLIBRARIES = libfrr.la
|
||||
libfrr_la_LDFLAGS = -version-info 0:0:0
|
||||
|
@ -85,13 +86,34 @@ pkginclude_HEADERS = \
|
|||
|
||||
noinst_HEADERS = \
|
||||
plist_int.h \
|
||||
log_int.h
|
||||
log_int.h \
|
||||
clippy.h \
|
||||
# end
|
||||
|
||||
noinst_PROGRAMS = grammar_sandbox
|
||||
if BUILD_CLIPPY
|
||||
noinst_PROGRAMS += clippy
|
||||
endif
|
||||
|
||||
grammar_sandbox_SOURCES = grammar_sandbox_main.c
|
||||
grammar_sandbox_LDADD = libfrr.la
|
||||
|
||||
clippy_SOURCES = \
|
||||
defun_lex.l \
|
||||
command_parse.y \
|
||||
command_lex.l \
|
||||
command_graph.c \
|
||||
command_py.c \
|
||||
memory.c \
|
||||
graph.c \
|
||||
vector.c \
|
||||
clippy.c \
|
||||
# end
|
||||
clippy_CPPFLAGS = -D_GNU_SOURCE
|
||||
clippy_CFLAGS = $(PYTHON_CFLAGS)
|
||||
clippy_LDADD = $(PYTHON_LIBS)
|
||||
clippy-command_graph.$(OBJEXT): route_types.h
|
||||
|
||||
EXTRA_DIST = \
|
||||
queue.h \
|
||||
command_lex.h \
|
||||
|
|
137
lib/clippy.c
Normal file
137
lib/clippy.c
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* clippy (CLI preparator in python) main executable
|
||||
* Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; see the file COPYING; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <Python.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <wchar.h>
|
||||
#include "getopt.h"
|
||||
|
||||
#include "command_graph.h"
|
||||
#include "clippy.h"
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define pychar wchar_t
|
||||
static wchar_t *wconv(const char *s)
|
||||
{
|
||||
size_t outlen = mbstowcs(NULL, s, 0);
|
||||
wchar_t *out = malloc((outlen + 1) * sizeof(wchar_t));
|
||||
mbstowcs(out, s, outlen + 1);
|
||||
out[outlen] = 0;
|
||||
return out;
|
||||
}
|
||||
#else
|
||||
#define pychar char
|
||||
#define wconv(x) x
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
pychar **wargv;
|
||||
|
||||
#if PY_VERSION_HEX >= 0x03040000 /* 3.4 */
|
||||
Py_SetStandardStreamEncoding("UTF-8", NULL);
|
||||
#endif
|
||||
Py_SetProgramName(wconv(argv[0]));
|
||||
PyImport_AppendInittab("_clippy", command_py_init);
|
||||
|
||||
Py_Initialize();
|
||||
|
||||
wargv = malloc(argc * sizeof(pychar *));
|
||||
for (int i = 1; i < argc; i++)
|
||||
wargv[i - 1] = wconv(argv[i]);
|
||||
PySys_SetArgv(argc - 1, wargv);
|
||||
|
||||
const char *pyfile = argc > 1 ? argv[1] : NULL;
|
||||
FILE *fp;
|
||||
if (pyfile) {
|
||||
fp = fopen(pyfile, "r");
|
||||
if (!fp) {
|
||||
fprintf(stderr, "%s: %s\n", pyfile, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
fp = stdin;
|
||||
char *ver = strdup(Py_GetVersion());
|
||||
char *cr = strchr(ver, '\n');
|
||||
if (cr)
|
||||
*cr = ' ';
|
||||
fprintf(stderr, "clippy interactive shell\n(Python %s)\n", ver);
|
||||
free(ver);
|
||||
PyRun_SimpleString("import rlcompleter, readline\n"
|
||||
"readline.parse_and_bind('tab: complete')");
|
||||
}
|
||||
|
||||
if (PyRun_AnyFile(fp, pyfile)) {
|
||||
if (PyErr_Occurred())
|
||||
PyErr_Print();
|
||||
else
|
||||
printf("unknown python failure (?)\n");
|
||||
return 1;
|
||||
}
|
||||
Py_Finalize();
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
for (int i = 1; i < argc; i++)
|
||||
free(wargv[i - 1]);
|
||||
#endif
|
||||
free(wargv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* and now for the ugly part... provide simplified logging functions so we
|
||||
* don't need to link libzebra (which would be a circular build dep) */
|
||||
|
||||
#ifdef __ASSERT_FUNCTION
|
||||
#undef __ASSERT_FUNCTION
|
||||
#endif
|
||||
|
||||
#include "log.h"
|
||||
#include "zassert.h"
|
||||
|
||||
#define ZLOG_FUNC(FUNCNAME) \
|
||||
void FUNCNAME(const char *format, ...) \
|
||||
{ \
|
||||
va_list args; \
|
||||
va_start(args, format); \
|
||||
vfprintf (stderr, format, args); \
|
||||
fputs ("\n", stderr); \
|
||||
va_end(args); \
|
||||
}
|
||||
|
||||
ZLOG_FUNC(zlog_err)
|
||||
ZLOG_FUNC(zlog_warn)
|
||||
ZLOG_FUNC(zlog_info)
|
||||
ZLOG_FUNC(zlog_notice)
|
||||
ZLOG_FUNC(zlog_debug)
|
||||
|
||||
void
|
||||
_zlog_assert_failed (const char *assertion, const char *file,
|
||||
unsigned int line, const char *function)
|
||||
{
|
||||
fprintf(stderr, "Assertion `%s' failed in file %s, line %u, function %s",
|
||||
assertion, file, line, (function ? function : "?"));
|
||||
abort();
|
||||
}
|
||||
|
||||
void memory_oom (size_t size, const char *name)
|
||||
{
|
||||
abort();
|
||||
}
|
28
lib/clippy.h
Normal file
28
lib/clippy.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* clippy (CLI preparator in python)
|
||||
* Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; see the file COPYING; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef _FRR_CLIPPY_H
|
||||
#define _FRR_CLIPPY_H
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
extern PyObject *clippy_parse(PyObject *self, PyObject *args);
|
||||
extern PyMODINIT_FUNC command_py_init(void);
|
||||
|
||||
#endif /* _FRR_CLIPPY_H */
|
336
lib/command_py.c
Normal file
336
lib/command_py.c
Normal file
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
* clippy (CLI preparator in python) wrapper for FRR command_graph
|
||||
* Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; see the file COPYING; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/* note: this wrapper is intended to be used as build-time helper. while
|
||||
* it should be generally correct and proper, there may be the occasional
|
||||
* memory leak or SEGV for things that haven't been well-tested.
|
||||
*/
|
||||
|
||||
#include <Python.h>
|
||||
#include "structmember.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "command_graph.h"
|
||||
#include "clippy.h"
|
||||
|
||||
struct wrap_graph;
|
||||
static PyObject *graph_to_pyobj(struct wrap_graph *graph, struct graph_node *gn);
|
||||
|
||||
/*
|
||||
* nodes are wrapped as follows:
|
||||
* - instances can only be acquired from a graph
|
||||
* - the same node will return the same wrapper object (they're buffered
|
||||
* through "idx")
|
||||
* - a reference is held onto the graph
|
||||
* - fields are copied for easy access with PyMemberDef
|
||||
*/
|
||||
struct wrap_graph_node {
|
||||
PyObject_HEAD
|
||||
|
||||
bool allowrepeat;
|
||||
const char *type;
|
||||
|
||||
bool deprecated;
|
||||
bool hidden;
|
||||
const char *text;
|
||||
const char *desc;
|
||||
const char *varname;
|
||||
long long min, max;
|
||||
|
||||
struct graph_node *node;
|
||||
struct wrap_graph *wgraph;
|
||||
size_t idx;
|
||||
};
|
||||
|
||||
/*
|
||||
* graphs are wrapped as follows:
|
||||
* - they can only be created by parsing a definition string
|
||||
* - there's a table here for the wrapped nodes (nodewrappers), indexed
|
||||
* by "idx" (corresponds to node's position in graph's table of nodes)
|
||||
* - graphs do NOT hold references to nodes (would be circular)
|
||||
*/
|
||||
struct wrap_graph {
|
||||
PyObject_HEAD
|
||||
|
||||
char *definition;
|
||||
struct graph *graph;
|
||||
struct wrap_graph_node **nodewrappers;
|
||||
};
|
||||
|
||||
static PyObject *refuse_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
PyErr_SetString(PyExc_ValueError, "cannot create instances of this type");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define member(name, type) {(char *)#name, type, offsetof(struct wrap_graph_node, name), READONLY, \
|
||||
(char *)#name " (" #type ")"}
|
||||
static PyMemberDef members_graph_node[] = {
|
||||
member(allowrepeat, T_BOOL),
|
||||
member(type, T_STRING),
|
||||
member(deprecated, T_BOOL),
|
||||
member(hidden, T_BOOL),
|
||||
member(text, T_STRING),
|
||||
member(desc, T_STRING),
|
||||
member(min, T_LONGLONG),
|
||||
member(max, T_LONGLONG),
|
||||
member(varname, T_STRING),
|
||||
{},
|
||||
};
|
||||
#undef member
|
||||
|
||||
/*
|
||||
* node.next() -- returns list of all "next" nodes.
|
||||
* this will include circles if the graph has them.
|
||||
*/
|
||||
static PyObject *graph_node_next(PyObject *self, PyObject *args)
|
||||
{
|
||||
struct wrap_graph_node *wrap = (struct wrap_graph_node *)self;
|
||||
PyObject *pylist;
|
||||
|
||||
if (wrap->node->data
|
||||
&& ((struct cmd_token *)wrap->node->data)->type == END_TKN)
|
||||
return PyList_New(0);
|
||||
pylist = PyList_New(vector_active(wrap->node->to));
|
||||
for (size_t i = 0; i < vector_active(wrap->node->to); i++) {
|
||||
struct graph_node *gn = vector_slot(wrap->node->to, i);
|
||||
PyList_SetItem(pylist, i, graph_to_pyobj(wrap->wgraph, gn));
|
||||
}
|
||||
return pylist;
|
||||
};
|
||||
|
||||
/*
|
||||
* node.join() -- return FORK's JOIN node or None
|
||||
*/
|
||||
static PyObject *graph_node_join(PyObject *self, PyObject *args)
|
||||
{
|
||||
struct wrap_graph_node *wrap = (struct wrap_graph_node *)self;
|
||||
|
||||
if (!wrap->node->data
|
||||
|| ((struct cmd_token *)wrap->node->data)->type == END_TKN)
|
||||
Py_RETURN_NONE;
|
||||
|
||||
struct cmd_token *tok = wrap->node->data;
|
||||
if (tok->type != FORK_TKN)
|
||||
Py_RETURN_NONE;
|
||||
|
||||
return graph_to_pyobj(wrap->wgraph, tok->forkjoin);
|
||||
};
|
||||
|
||||
static PyMethodDef methods_graph_node[] = {
|
||||
{"next", graph_node_next, METH_NOARGS, "outbound graph edge list"},
|
||||
{"join", graph_node_join, METH_NOARGS, "outbound join node"},
|
||||
{}
|
||||
};
|
||||
|
||||
static void graph_node_wrap_free(void *arg)
|
||||
{
|
||||
struct wrap_graph_node *wrap = arg;
|
||||
wrap->wgraph->nodewrappers[wrap->idx] = NULL;
|
||||
Py_DECREF(wrap->wgraph);
|
||||
}
|
||||
|
||||
static PyTypeObject typeobj_graph_node = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "_clippy.GraphNode",
|
||||
.tp_basicsize = sizeof(struct wrap_graph_node),
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_doc = "struct graph_node *",
|
||||
.tp_new = refuse_new,
|
||||
.tp_free = graph_node_wrap_free,
|
||||
.tp_members = members_graph_node,
|
||||
.tp_methods = methods_graph_node,
|
||||
};
|
||||
|
||||
static PyObject *graph_to_pyobj(struct wrap_graph *wgraph, struct graph_node *gn)
|
||||
{
|
||||
struct wrap_graph_node *wrap;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < vector_active(wgraph->graph->nodes); i++)
|
||||
if (vector_slot(wgraph->graph->nodes, i) == gn)
|
||||
break;
|
||||
if (i == vector_active(wgraph->graph->nodes)) {
|
||||
PyErr_SetString(PyExc_ValueError, "cannot find node in graph");
|
||||
return NULL;
|
||||
}
|
||||
if (wgraph->nodewrappers[i]) {
|
||||
PyObject *obj = (PyObject *)wgraph->nodewrappers[i];
|
||||
Py_INCREF(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
wrap = (struct wrap_graph_node *)typeobj_graph_node.tp_alloc(&typeobj_graph_node, 0);
|
||||
if (!wrap)
|
||||
return NULL;
|
||||
wgraph->nodewrappers[i] = wrap;
|
||||
Py_INCREF(wgraph);
|
||||
|
||||
wrap->idx = i;
|
||||
wrap->wgraph = wgraph;
|
||||
wrap->node = gn;
|
||||
wrap->type = "NULL";
|
||||
wrap->allowrepeat = false;
|
||||
if (gn->data) {
|
||||
struct cmd_token *tok = gn->data;
|
||||
switch (tok->type) {
|
||||
#define item(x) case x: wrap->type = #x; break;
|
||||
item(WORD_TKN) // words
|
||||
item(VARIABLE_TKN) // almost anything
|
||||
item(RANGE_TKN) // integer range
|
||||
item(IPV4_TKN) // IPV4 addresses
|
||||
item(IPV4_PREFIX_TKN) // IPV4 network prefixes
|
||||
item(IPV6_TKN) // IPV6 prefixes
|
||||
item(IPV6_PREFIX_TKN) // IPV6 network prefixes
|
||||
|
||||
/* plumbing types */
|
||||
item(FORK_TKN)
|
||||
item(JOIN_TKN)
|
||||
item(START_TKN)
|
||||
item(END_TKN)
|
||||
default:
|
||||
wrap->type = "???";
|
||||
}
|
||||
|
||||
wrap->deprecated = (tok->attr == CMD_ATTR_DEPRECATED);
|
||||
wrap->hidden = (tok->attr == CMD_ATTR_HIDDEN);
|
||||
wrap->text = tok->text;
|
||||
wrap->desc = tok->desc;
|
||||
wrap->varname = tok->varname;
|
||||
wrap->min = tok->min;
|
||||
wrap->max = tok->max;
|
||||
wrap->allowrepeat = tok->allowrepeat;
|
||||
}
|
||||
|
||||
return (PyObject *)wrap;
|
||||
}
|
||||
|
||||
#define member(name, type) {(char *)#name, type, offsetof(struct wrap_graph, name), READONLY, \
|
||||
(char *)#name " (" #type ")"}
|
||||
static PyMemberDef members_graph[] = {
|
||||
member(definition, T_STRING),
|
||||
{},
|
||||
};
|
||||
#undef member
|
||||
|
||||
/* graph.first() - root node */
|
||||
static PyObject *graph_first(PyObject *self, PyObject *args)
|
||||
{
|
||||
struct wrap_graph *gwrap = (struct wrap_graph *)self;
|
||||
struct graph_node *gn = vector_slot(gwrap->graph->nodes, 0);
|
||||
return graph_to_pyobj(gwrap, gn);
|
||||
};
|
||||
|
||||
static PyMethodDef methods_graph[] = {
|
||||
{"first", graph_first, METH_NOARGS, "first graph node"},
|
||||
{}
|
||||
};
|
||||
|
||||
static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds);
|
||||
|
||||
static void graph_wrap_free(void *arg)
|
||||
{
|
||||
struct wrap_graph *wgraph = arg;
|
||||
free(wgraph->nodewrappers);
|
||||
free(wgraph->definition);
|
||||
}
|
||||
|
||||
static PyTypeObject typeobj_graph = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "_clippy.Graph",
|
||||
.tp_basicsize = sizeof(struct wrap_graph),
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_doc = "struct graph *",
|
||||
.tp_new = graph_parse,
|
||||
.tp_free = graph_wrap_free,
|
||||
.tp_members = members_graph,
|
||||
.tp_methods = methods_graph,
|
||||
};
|
||||
|
||||
/* top call / entrypoint for python code */
|
||||
static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
const char *def, *doc = NULL;
|
||||
struct wrap_graph *gwrap;
|
||||
static const char *kwnames[] = { "cmddef", "doc", NULL };
|
||||
|
||||
gwrap = (struct wrap_graph *)typeobj_graph.tp_alloc(&typeobj_graph, 0);
|
||||
if (!gwrap)
|
||||
return NULL;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s", (char **)kwnames, &def, &doc))
|
||||
return NULL;
|
||||
|
||||
struct graph *graph = graph_new ();
|
||||
struct cmd_token *token = cmd_token_new (START_TKN, 0, NULL, NULL);
|
||||
graph_new_node (graph, token, (void (*)(void *)) &cmd_token_del);
|
||||
|
||||
struct cmd_element cmd = { .string = def, .doc = doc };
|
||||
cmd_graph_parse (graph, &cmd);
|
||||
cmd_graph_names (graph);
|
||||
|
||||
gwrap->graph = graph;
|
||||
gwrap->definition = strdup(def);
|
||||
gwrap->nodewrappers = calloc(vector_active(graph->nodes),
|
||||
sizeof (gwrap->nodewrappers[0]));
|
||||
return (PyObject *)gwrap;
|
||||
}
|
||||
|
||||
static PyMethodDef clippy_methods[] = {
|
||||
{"parse", clippy_parse, METH_VARARGS, "Parse a C file"},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
static struct PyModuleDef pymoddef_clippy = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_clippy",
|
||||
NULL, /* docstring */
|
||||
-1,
|
||||
clippy_methods,
|
||||
};
|
||||
#define modcreate() PyModule_Create(&pymoddef_clippy)
|
||||
#define initret(val) return val;
|
||||
#else
|
||||
#define modcreate() Py_InitModule("_clippy", clippy_methods)
|
||||
#define initret(val) do { \
|
||||
if (!val) Py_FatalError("initialization failure"); \
|
||||
return; } while (0)
|
||||
#endif
|
||||
|
||||
PyMODINIT_FUNC command_py_init(void)
|
||||
{
|
||||
PyObject* pymod;
|
||||
|
||||
if (PyType_Ready(&typeobj_graph_node) < 0)
|
||||
initret(NULL);
|
||||
if (PyType_Ready(&typeobj_graph) < 0)
|
||||
initret(NULL);
|
||||
|
||||
pymod = modcreate();
|
||||
if (!pymod)
|
||||
initret(NULL);
|
||||
|
||||
Py_INCREF(&typeobj_graph_node);
|
||||
PyModule_AddObject(pymod, "GraphNode", (PyObject *)&typeobj_graph_node);
|
||||
Py_INCREF(&typeobj_graph);
|
||||
PyModule_AddObject(pymod, "Graph", (PyObject *)&typeobj_graph);
|
||||
initret(pymod);
|
||||
}
|
265
lib/defun_lex.l
Normal file
265
lib/defun_lex.l
Normal file
|
@ -0,0 +1,265 @@
|
|||
%{
|
||||
/*
|
||||
* clippy (CLI preparator in python) C pseudo-lexer
|
||||
* Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; see the file COPYING; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/* This is just enough of a lexer to make rough sense of a C source file.
|
||||
* It handles C preprocessor directives, strings, and looks for FRR-specific
|
||||
* idioms (aka DEFUN).
|
||||
*
|
||||
* There is some preliminary support for documentation comments for DEFUNs.
|
||||
* They would look like this (note the ~): (replace \ by /)
|
||||
*
|
||||
* \*~ documentation for foobar_cmd
|
||||
* * parameter does xyz
|
||||
* *\
|
||||
* DEFUN(foobar_cmd, ...)
|
||||
*
|
||||
* This is intended for user documentation / command reference. Don't put
|
||||
* code documentation in it.
|
||||
*/
|
||||
|
||||
/* ignore harmless bug in old versions of flex */
|
||||
#pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
|
||||
#include "config.h"
|
||||
#include <Python.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "command_graph.h"
|
||||
#include "clippy.h"
|
||||
|
||||
#define ID 258
|
||||
#define PREPROC 259
|
||||
#define OPERATOR 260
|
||||
#define STRING 261
|
||||
#define COMMENT 262
|
||||
#define SPECIAL 263
|
||||
|
||||
#define DEFUNNY 270
|
||||
#define INSTALL 271
|
||||
#define AUXILIARY 272
|
||||
|
||||
int comment_link;
|
||||
char string_end;
|
||||
|
||||
char *value;
|
||||
|
||||
static void extendbuf(char **what, const char *arg)
|
||||
{
|
||||
if (!*what)
|
||||
*what = strdup(arg);
|
||||
else {
|
||||
size_t vall = strlen(*what), argl = strlen(arg);
|
||||
*what = realloc(*what, vall + argl + 1);
|
||||
memcpy(*what + vall, arg, argl);
|
||||
(*what)[vall + argl] = '\0';
|
||||
}
|
||||
}
|
||||
#define extend(x) extendbuf(&value, x)
|
||||
|
||||
%}
|
||||
|
||||
ID [A-Za-z0-9_]+
|
||||
OPERATOR [!%&/\[\]{}=?:^|\*.;><~'\\+-]
|
||||
SPECIAL [(),]
|
||||
|
||||
%pointer
|
||||
%option yylineno
|
||||
%option noyywrap
|
||||
%option noinput
|
||||
%option nounput
|
||||
%option outfile="defun_lex.c"
|
||||
%option prefix="def_yy"
|
||||
%option 8bit
|
||||
|
||||
%s linestart
|
||||
%x comment
|
||||
%x linecomment
|
||||
%x preproc
|
||||
%x rstring
|
||||
%%
|
||||
BEGIN(linestart);
|
||||
|
||||
\n BEGIN(linestart);
|
||||
|
||||
<INITIAL,linestart,preproc>"/*" comment_link = YY_START; extend(yytext); BEGIN(comment);
|
||||
<comment>[^*\n]* extend(yytext);
|
||||
<comment>"*"+[^*/\n]* extend(yytext);
|
||||
<comment>\n extend(yytext);
|
||||
<comment>"*"+"/" extend(yytext); BEGIN(comment_link); return COMMENT;
|
||||
|
||||
<INITIAL,linestart,preproc>"//" comment_link = YY_START; extend(yytext); BEGIN(linecomment);
|
||||
<linecomment>[^\n]* extend(yytext);
|
||||
<linecomment>\n BEGIN((comment_link == INITIAL) ? linestart : comment_link); return COMMENT;
|
||||
|
||||
<linestart># BEGIN(preproc);
|
||||
<preproc>\n BEGIN(INITIAL); return PREPROC;
|
||||
<preproc>[^\n\\]+ extend(yytext);
|
||||
<preproc>\\\n extend(yytext);
|
||||
<preproc>\\+[^\n] extend(yytext);
|
||||
|
||||
[\"\'] string_end = yytext[0]; extend(yytext); BEGIN(rstring);
|
||||
<rstring>[\"\'] {
|
||||
extend(yytext);
|
||||
if (yytext[0] == string_end) {
|
||||
BEGIN(INITIAL);
|
||||
return STRING;
|
||||
}
|
||||
}
|
||||
<rstring>\\\n /* ignore */
|
||||
<rstring>\\. extend(yytext);
|
||||
<rstring>[^\\\"\']+ extend(yytext);
|
||||
|
||||
"DEFUN" value = strdup(yytext); return DEFUNNY;
|
||||
"DEFUN_NOSH" value = strdup(yytext); return DEFUNNY;
|
||||
"DEFUN_HIDDEN" value = strdup(yytext); return DEFUNNY;
|
||||
"DEFPY" value = strdup(yytext); return DEFUNNY;
|
||||
"ALIAS" value = strdup(yytext); return DEFUNNY;
|
||||
"ALIAS_HIDDEN" value = strdup(yytext); return DEFUNNY;
|
||||
"install_element" value = strdup(yytext); return INSTALL;
|
||||
"VTYSH_TARGETS" value = strdup(yytext); return AUXILIARY;
|
||||
"VTYSH_NODESWITCH" value = strdup(yytext); return AUXILIARY;
|
||||
|
||||
[ \t\n]+ /* ignore */
|
||||
\\ /* ignore */
|
||||
{ID} BEGIN(INITIAL); value = strdup(yytext); return ID;
|
||||
{OPERATOR} BEGIN(INITIAL); value = strdup(yytext); return OPERATOR;
|
||||
{SPECIAL} BEGIN(INITIAL); value = strdup(yytext); return SPECIAL;
|
||||
. /* printf("-- '%s' in init\n", yytext); */ BEGIN(INITIAL); return yytext[0];
|
||||
|
||||
%%
|
||||
|
||||
static int yylex_clr(char **retbuf)
|
||||
{
|
||||
int rv = def_yylex();
|
||||
*retbuf = value;
|
||||
value = NULL;
|
||||
return rv;
|
||||
}
|
||||
|
||||
static PyObject *get_args(void)
|
||||
{
|
||||
PyObject *pyObj = PyList_New(0);
|
||||
PyObject *pyArg = NULL;
|
||||
|
||||
char *tval;
|
||||
int depth = 1;
|
||||
int token;
|
||||
|
||||
while ((token = yylex_clr(&tval)) != YY_NULL) {
|
||||
if (token == SPECIAL && tval[0] == '(') {
|
||||
free(tval);
|
||||
break;
|
||||
}
|
||||
if (token == COMMENT) {
|
||||
free(tval);
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr, "invalid input!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while ((token = yylex_clr(&tval)) != YY_NULL) {
|
||||
if (token == COMMENT) {
|
||||
free(tval);
|
||||
continue;
|
||||
}
|
||||
if (token == SPECIAL) {
|
||||
if (depth == 1 && (tval[0] == ',' || tval[0] == ')')) {
|
||||
if (pyArg)
|
||||
PyList_Append(pyObj, pyArg);
|
||||
pyArg = NULL;
|
||||
if (tval[0] == ')') {
|
||||
free(tval);
|
||||
break;
|
||||
}
|
||||
free(tval);
|
||||
continue;
|
||||
}
|
||||
if (tval[0] == '(')
|
||||
depth++;
|
||||
if (tval[0] == ')')
|
||||
depth--;
|
||||
}
|
||||
if (!pyArg)
|
||||
pyArg = PyList_New(0);
|
||||
PyList_Append(pyArg, PyUnicode_FromString(tval));
|
||||
free(tval);
|
||||
}
|
||||
return pyObj;
|
||||
}
|
||||
|
||||
/* _clippy.parse() -- read a C file, returning a list of interesting bits.
|
||||
* note this ditches most of the actual C code. */
|
||||
PyObject *clippy_parse(PyObject *self, PyObject *args)
|
||||
{
|
||||
const char *filename;
|
||||
if (!PyArg_ParseTuple(args, "s", &filename))
|
||||
return NULL;
|
||||
|
||||
FILE *fd = fopen(filename, "r");
|
||||
if (!fd)
|
||||
return PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
|
||||
|
||||
char *tval;
|
||||
int token;
|
||||
yyin = fd;
|
||||
value = NULL;
|
||||
|
||||
PyObject *pyCont = PyDict_New();
|
||||
PyObject *pyObj = PyList_New(0);
|
||||
PyDict_SetItemString(pyCont, "filename", PyUnicode_FromString(filename));
|
||||
PyDict_SetItemString(pyCont, "data", pyObj);
|
||||
|
||||
while ((token = yylex_clr(&tval)) != YY_NULL) {
|
||||
int lineno = yylineno;
|
||||
PyObject *pyItem = NULL, *pyArgs;
|
||||
switch (token) {
|
||||
case DEFUNNY:
|
||||
case INSTALL:
|
||||
case AUXILIARY:
|
||||
pyArgs = get_args();
|
||||
pyItem = PyDict_New();
|
||||
PyDict_SetItemString(pyItem, "type", PyUnicode_FromString(tval));
|
||||
PyDict_SetItemString(pyItem, "args", pyArgs);
|
||||
break;
|
||||
case COMMENT:
|
||||
if (strncmp(tval, "//~", 3) && strncmp(tval, "/*~", 3))
|
||||
break;
|
||||
pyItem = PyDict_New();
|
||||
PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("COMMENT"));
|
||||
PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval));
|
||||
break;
|
||||
case PREPROC:
|
||||
pyItem = PyDict_New();
|
||||
PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("PREPROC"));
|
||||
PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval));
|
||||
break;
|
||||
}
|
||||
if (pyItem) {
|
||||
PyDict_SetItemString(pyItem, "lineno", PyLong_FromLong(lineno));
|
||||
PyList_Append(pyObj, pyItem);
|
||||
}
|
||||
free(tval);
|
||||
}
|
||||
def_yylex_destroy();
|
||||
fclose(fd);
|
||||
return pyCont;
|
||||
}
|
Loading…
Reference in a new issue