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.
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.
#
@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
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 \
--- /dev/null
+/*
+ * 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();
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+%{
+/*
+ * 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;
+}