]>
Commit | Line | Data |
---|---|---|
29ad6f68 DL |
1 | /* |
2 | * clippy (CLI preparator in python) C pseudo-lexer | |
3 | * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc. | |
4 | * | |
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) | |
8 | * any later version. | |
9 | * | |
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 | |
13 | * more details. | |
14 | * | |
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 | |
18 | */ | |
19 | ||
20 | /* This is just enough of a lexer to make rough sense of a C source file. | |
21 | * It handles C preprocessor directives, strings, and looks for FRR-specific | |
22 | * idioms (aka DEFUN). | |
23 | * | |
24 | * There is some preliminary support for documentation comments for DEFUNs. | |
25 | * They would look like this (note the ~): (replace \ by /) | |
26 | * | |
27 | * \*~ documentation for foobar_cmd | |
28 | * * parameter does xyz | |
29 | * *\ | |
30 | * DEFUN(foobar_cmd, ...) | |
31 | * | |
32 | * This is intended for user documentation / command reference. Don't put | |
33 | * code documentation in it. | |
34 | */ | |
35 | ||
7b34167d DL |
36 | %top{ |
37 | #ifdef HAVE_CONFIG_H | |
38 | #include "config.h" | |
39 | #endif | |
40 | } | |
41 | %{ | |
a37bd5e0 | 42 | /* ignore harmless bugs in old versions of flex */ |
29ad6f68 | 43 | #pragma GCC diagnostic ignored "-Wsign-compare" |
a37bd5e0 | 44 | #pragma GCC diagnostic ignored "-Wunused-value" |
29ad6f68 DL |
45 | |
46 | #include "config.h" | |
47 | #include <Python.h> | |
48 | #include <string.h> | |
49 | #include <stdlib.h> | |
50 | ||
51 | #include "command_graph.h" | |
52 | #include "clippy.h" | |
53 | ||
54 | #define ID 258 | |
55 | #define PREPROC 259 | |
56 | #define OPERATOR 260 | |
57 | #define STRING 261 | |
58 | #define COMMENT 262 | |
59 | #define SPECIAL 263 | |
60 | ||
61 | #define DEFUNNY 270 | |
62 | #define INSTALL 271 | |
63 | #define AUXILIARY 272 | |
64 | ||
65 | int comment_link; | |
66 | char string_end; | |
67 | ||
68 | char *value; | |
69 | ||
70 | static void extendbuf(char **what, const char *arg) | |
71 | { | |
72 | if (!*what) | |
73 | *what = strdup(arg); | |
74 | else { | |
75 | size_t vall = strlen(*what), argl = strlen(arg); | |
76 | *what = realloc(*what, vall + argl + 1); | |
77 | memcpy(*what + vall, arg, argl); | |
78 | (*what)[vall + argl] = '\0'; | |
79 | } | |
80 | } | |
81 | #define extend(x) extendbuf(&value, x) | |
82 | ||
83 | %} | |
84 | ||
85 | ID [A-Za-z0-9_]+ | |
86 | OPERATOR [!%&/\[\]{}=?:^|\*.;><~'\\+-] | |
87 | SPECIAL [(),] | |
88 | ||
89 | %pointer | |
90 | %option yylineno | |
91 | %option noyywrap | |
92 | %option noinput | |
93 | %option nounput | |
4a121f99 | 94 | %option outfile="lib/defun_lex.c" |
29ad6f68 DL |
95 | %option prefix="def_yy" |
96 | %option 8bit | |
97 | ||
98 | %s linestart | |
99 | %x comment | |
100 | %x linecomment | |
101 | %x preproc | |
102 | %x rstring | |
103 | %% | |
104 | BEGIN(linestart); | |
105 | ||
106 | \n BEGIN(linestart); | |
107 | ||
108 | <INITIAL,linestart,preproc>"/*" comment_link = YY_START; extend(yytext); BEGIN(comment); | |
109 | <comment>[^*\n]* extend(yytext); | |
110 | <comment>"*"+[^*/\n]* extend(yytext); | |
111 | <comment>\n extend(yytext); | |
112 | <comment>"*"+"/" extend(yytext); BEGIN(comment_link); return COMMENT; | |
113 | ||
114 | <INITIAL,linestart,preproc>"//" comment_link = YY_START; extend(yytext); BEGIN(linecomment); | |
115 | <linecomment>[^\n]* extend(yytext); | |
116 | <linecomment>\n BEGIN((comment_link == INITIAL) ? linestart : comment_link); return COMMENT; | |
117 | ||
118 | <linestart># BEGIN(preproc); | |
119 | <preproc>\n BEGIN(INITIAL); return PREPROC; | |
120 | <preproc>[^\n\\]+ extend(yytext); | |
121 | <preproc>\\\n extend(yytext); | |
122 | <preproc>\\+[^\n] extend(yytext); | |
123 | ||
124 | [\"\'] string_end = yytext[0]; extend(yytext); BEGIN(rstring); | |
125 | <rstring>[\"\'] { | |
126 | extend(yytext); | |
127 | if (yytext[0] == string_end) { | |
128 | BEGIN(INITIAL); | |
129 | return STRING; | |
130 | } | |
131 | } | |
132 | <rstring>\\\n /* ignore */ | |
133 | <rstring>\\. extend(yytext); | |
134 | <rstring>[^\\\"\']+ extend(yytext); | |
135 | ||
136 | "DEFUN" value = strdup(yytext); return DEFUNNY; | |
137 | "DEFUN_NOSH" value = strdup(yytext); return DEFUNNY; | |
138 | "DEFUN_HIDDEN" value = strdup(yytext); return DEFUNNY; | |
139 | "DEFPY" value = strdup(yytext); return DEFUNNY; | |
26d19c83 | 140 | "DEFPY_NOSH" value = strdup(yytext); return DEFUNNY; |
e31f4dbe QY |
141 | "DEFPY_ATTR" value = strdup(yytext); return DEFUNNY; |
142 | "DEFPY_HIDDEN" value = strdup(yytext); return DEFUNNY; | |
29ad6f68 DL |
143 | "ALIAS" value = strdup(yytext); return DEFUNNY; |
144 | "ALIAS_HIDDEN" value = strdup(yytext); return DEFUNNY; | |
145 | "install_element" value = strdup(yytext); return INSTALL; | |
146 | "VTYSH_TARGETS" value = strdup(yytext); return AUXILIARY; | |
147 | "VTYSH_NODESWITCH" value = strdup(yytext); return AUXILIARY; | |
148 | ||
149 | [ \t\n]+ /* ignore */ | |
150 | \\ /* ignore */ | |
151 | {ID} BEGIN(INITIAL); value = strdup(yytext); return ID; | |
152 | {OPERATOR} BEGIN(INITIAL); value = strdup(yytext); return OPERATOR; | |
153 | {SPECIAL} BEGIN(INITIAL); value = strdup(yytext); return SPECIAL; | |
154 | . /* printf("-- '%s' in init\n", yytext); */ BEGIN(INITIAL); return yytext[0]; | |
155 | ||
156 | %% | |
157 | ||
158 | static int yylex_clr(char **retbuf) | |
159 | { | |
160 | int rv = def_yylex(); | |
161 | *retbuf = value; | |
162 | value = NULL; | |
163 | return rv; | |
164 | } | |
165 | ||
3779776a | 166 | static PyObject *get_args(const char *filename, int lineno) |
29ad6f68 DL |
167 | { |
168 | PyObject *pyObj = PyList_New(0); | |
169 | PyObject *pyArg = NULL; | |
170 | ||
171 | char *tval; | |
172 | int depth = 1; | |
173 | int token; | |
174 | ||
175 | while ((token = yylex_clr(&tval)) != YY_NULL) { | |
176 | if (token == SPECIAL && tval[0] == '(') { | |
177 | free(tval); | |
178 | break; | |
179 | } | |
180 | if (token == COMMENT) { | |
181 | free(tval); | |
182 | continue; | |
183 | } | |
184 | fprintf(stderr, "invalid input!\n"); | |
185 | exit(1); | |
186 | } | |
187 | ||
188 | while ((token = yylex_clr(&tval)) != YY_NULL) { | |
189 | if (token == COMMENT) { | |
190 | free(tval); | |
191 | continue; | |
192 | } | |
3779776a DL |
193 | if (token == PREPROC) { |
194 | free(tval); | |
195 | Py_DECREF(pyObj); | |
196 | return PyErr_Format(PyExc_ValueError, | |
197 | "%s:%d: cannot process CPP directive within argument list", | |
198 | filename, lineno); | |
199 | } | |
29ad6f68 DL |
200 | if (token == SPECIAL) { |
201 | if (depth == 1 && (tval[0] == ',' || tval[0] == ')')) { | |
202 | if (pyArg) | |
203 | PyList_Append(pyObj, pyArg); | |
204 | pyArg = NULL; | |
205 | if (tval[0] == ')') { | |
206 | free(tval); | |
207 | break; | |
208 | } | |
209 | free(tval); | |
210 | continue; | |
211 | } | |
212 | if (tval[0] == '(') | |
213 | depth++; | |
214 | if (tval[0] == ')') | |
215 | depth--; | |
216 | } | |
217 | if (!pyArg) | |
218 | pyArg = PyList_New(0); | |
219 | PyList_Append(pyArg, PyUnicode_FromString(tval)); | |
220 | free(tval); | |
221 | } | |
222 | return pyObj; | |
223 | } | |
224 | ||
225 | /* _clippy.parse() -- read a C file, returning a list of interesting bits. | |
226 | * note this ditches most of the actual C code. */ | |
227 | PyObject *clippy_parse(PyObject *self, PyObject *args) | |
228 | { | |
229 | const char *filename; | |
230 | if (!PyArg_ParseTuple(args, "s", &filename)) | |
231 | return NULL; | |
232 | ||
233 | FILE *fd = fopen(filename, "r"); | |
234 | if (!fd) | |
235 | return PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename); | |
236 | ||
237 | char *tval; | |
238 | int token; | |
239 | yyin = fd; | |
240 | value = NULL; | |
241 | ||
242 | PyObject *pyCont = PyDict_New(); | |
243 | PyObject *pyObj = PyList_New(0); | |
244 | PyDict_SetItemString(pyCont, "filename", PyUnicode_FromString(filename)); | |
245 | PyDict_SetItemString(pyCont, "data", pyObj); | |
246 | ||
247 | while ((token = yylex_clr(&tval)) != YY_NULL) { | |
248 | int lineno = yylineno; | |
249 | PyObject *pyItem = NULL, *pyArgs; | |
250 | switch (token) { | |
251 | case DEFUNNY: | |
252 | case INSTALL: | |
253 | case AUXILIARY: | |
3779776a DL |
254 | pyArgs = get_args(filename, lineno); |
255 | if (!pyArgs) { | |
256 | free(tval); | |
257 | Py_DECREF(pyCont); | |
258 | return NULL; | |
259 | } | |
29ad6f68 DL |
260 | pyItem = PyDict_New(); |
261 | PyDict_SetItemString(pyItem, "type", PyUnicode_FromString(tval)); | |
262 | PyDict_SetItemString(pyItem, "args", pyArgs); | |
263 | break; | |
264 | case COMMENT: | |
265 | if (strncmp(tval, "//~", 3) && strncmp(tval, "/*~", 3)) | |
266 | break; | |
267 | pyItem = PyDict_New(); | |
268 | PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("COMMENT")); | |
269 | PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval)); | |
270 | break; | |
271 | case PREPROC: | |
272 | pyItem = PyDict_New(); | |
273 | PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("PREPROC")); | |
274 | PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval)); | |
3779776a | 275 | lineno--; |
29ad6f68 DL |
276 | break; |
277 | } | |
278 | if (pyItem) { | |
279 | PyDict_SetItemString(pyItem, "lineno", PyLong_FromLong(lineno)); | |
280 | PyList_Append(pyObj, pyItem); | |
281 | } | |
282 | free(tval); | |
283 | } | |
284 | def_yylex_destroy(); | |
285 | fclose(fd); | |
286 | return pyCont; | |
287 | } |