]>
Commit | Line | Data |
---|---|---|
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 | ||
36 | %top{ | |
37 | #ifdef HAVE_CONFIG_H | |
38 | #include "config.h" | |
39 | #endif | |
40 | } | |
41 | %{ | |
42 | /* ignore harmless bugs in old versions of flex */ | |
43 | #pragma GCC diagnostic ignored "-Wsign-compare" | |
44 | #pragma GCC diagnostic ignored "-Wunused-value" | |
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 | |
94 | %option outfile="lib/defun_lex.c" | |
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; | |
140 | "DEFPY_NOSH" value = strdup(yytext); return DEFUNNY; | |
141 | "DEFPY_ATTR" value = strdup(yytext); return DEFUNNY; | |
142 | "DEFPY_HIDDEN" value = strdup(yytext); return DEFUNNY; | |
143 | "DEFPY_YANG" value = strdup(yytext); return DEFUNNY; | |
144 | "DEFPY_YANG_NOSH" value = strdup(yytext); return DEFUNNY; | |
145 | "ALIAS" value = strdup(yytext); return DEFUNNY; | |
146 | "ALIAS_HIDDEN" value = strdup(yytext); return DEFUNNY; | |
147 | "install_element" value = strdup(yytext); return INSTALL; | |
148 | "VTYSH_TARGETS" value = strdup(yytext); return AUXILIARY; | |
149 | "VTYSH_NODESWITCH" value = strdup(yytext); return AUXILIARY; | |
150 | ||
151 | [ \t\n]+ /* ignore */ | |
152 | \\ /* ignore */ | |
153 | {ID} BEGIN(INITIAL); value = strdup(yytext); return ID; | |
154 | {OPERATOR} BEGIN(INITIAL); value = strdup(yytext); return OPERATOR; | |
155 | {SPECIAL} BEGIN(INITIAL); value = strdup(yytext); return SPECIAL; | |
156 | . /* printf("-- '%s' in init\n", yytext); */ BEGIN(INITIAL); return yytext[0]; | |
157 | ||
158 | %% | |
159 | ||
160 | static int yylex_clr(char **retbuf) | |
161 | { | |
162 | int rv = def_yylex(); | |
163 | *retbuf = value; | |
164 | value = NULL; | |
165 | return rv; | |
166 | } | |
167 | ||
168 | static PyObject *get_args(const char *filename, int lineno) | |
169 | { | |
170 | PyObject *pyObj = PyList_New(0); | |
171 | PyObject *pyArg = NULL; | |
172 | ||
173 | char *tval; | |
174 | int depth = 1; | |
175 | int token; | |
176 | ||
177 | while ((token = yylex_clr(&tval)) != YY_NULL) { | |
178 | if (token == SPECIAL && tval[0] == '(') { | |
179 | free(tval); | |
180 | break; | |
181 | } | |
182 | if (token == COMMENT) { | |
183 | free(tval); | |
184 | continue; | |
185 | } | |
186 | fprintf(stderr, "invalid input!\n"); | |
187 | exit(1); | |
188 | } | |
189 | ||
190 | while ((token = yylex_clr(&tval)) != YY_NULL) { | |
191 | if (token == COMMENT) { | |
192 | free(tval); | |
193 | continue; | |
194 | } | |
195 | if (token == PREPROC) { | |
196 | free(tval); | |
197 | Py_DECREF(pyObj); | |
198 | return PyErr_Format(PyExc_ValueError, | |
199 | "%s:%d: cannot process CPP directive within argument list", | |
200 | filename, lineno); | |
201 | } | |
202 | if (token == SPECIAL) { | |
203 | if (depth == 1 && (tval[0] == ',' || tval[0] == ')')) { | |
204 | if (pyArg) | |
205 | PyList_Append(pyObj, pyArg); | |
206 | pyArg = NULL; | |
207 | if (tval[0] == ')') { | |
208 | free(tval); | |
209 | break; | |
210 | } | |
211 | free(tval); | |
212 | continue; | |
213 | } | |
214 | if (tval[0] == '(') | |
215 | depth++; | |
216 | if (tval[0] == ')') | |
217 | depth--; | |
218 | } | |
219 | if (!pyArg) | |
220 | pyArg = PyList_New(0); | |
221 | PyList_Append(pyArg, PyUnicode_FromString(tval)); | |
222 | free(tval); | |
223 | } | |
224 | return pyObj; | |
225 | } | |
226 | ||
227 | /* _clippy.parse() -- read a C file, returning a list of interesting bits. | |
228 | * note this ditches most of the actual C code. */ | |
229 | PyObject *clippy_parse(PyObject *self, PyObject *args) | |
230 | { | |
231 | const char *filename; | |
232 | if (!PyArg_ParseTuple(args, "s", &filename)) | |
233 | return NULL; | |
234 | ||
235 | FILE *fd = fopen(filename, "r"); | |
236 | if (!fd) | |
237 | return PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename); | |
238 | ||
239 | char *tval; | |
240 | int token; | |
241 | yyin = fd; | |
242 | value = NULL; | |
243 | ||
244 | PyObject *pyCont = PyDict_New(); | |
245 | PyObject *pyObj = PyList_New(0); | |
246 | PyDict_SetItemString(pyCont, "filename", PyUnicode_FromString(filename)); | |
247 | PyDict_SetItemString(pyCont, "data", pyObj); | |
248 | ||
249 | while ((token = yylex_clr(&tval)) != YY_NULL) { | |
250 | int lineno = yylineno; | |
251 | PyObject *pyItem = NULL, *pyArgs; | |
252 | switch (token) { | |
253 | case DEFUNNY: | |
254 | case INSTALL: | |
255 | case AUXILIARY: | |
256 | pyArgs = get_args(filename, lineno); | |
257 | if (!pyArgs) { | |
258 | free(tval); | |
259 | Py_DECREF(pyCont); | |
260 | return NULL; | |
261 | } | |
262 | pyItem = PyDict_New(); | |
263 | PyDict_SetItemString(pyItem, "type", PyUnicode_FromString(tval)); | |
264 | PyDict_SetItemString(pyItem, "args", pyArgs); | |
265 | break; | |
266 | case COMMENT: | |
267 | if (strncmp(tval, "//~", 3) && strncmp(tval, "/*~", 3)) | |
268 | break; | |
269 | pyItem = PyDict_New(); | |
270 | PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("COMMENT")); | |
271 | PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval)); | |
272 | break; | |
273 | case PREPROC: | |
274 | pyItem = PyDict_New(); | |
275 | PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("PREPROC")); | |
276 | PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval)); | |
277 | lineno--; | |
278 | break; | |
279 | } | |
280 | if (pyItem) { | |
281 | PyDict_SetItemString(pyItem, "lineno", PyLong_FromLong(lineno)); | |
282 | PyList_Append(pyObj, pyItem); | |
283 | } | |
284 | free(tval); | |
285 | } | |
286 | def_yylex_destroy(); | |
287 | fclose(fd); | |
288 | return pyCont; | |
289 | } |