Annotation of src/usr.bin/lex/filter.c, Revision 1.8
1.8 ! tedu 1: /* $OpenBSD: filter.c,v 1.7 2016/12/18 06:11:23 krw Exp $ */
1.1 tedu 2:
3: /* filter - postprocessing of flex output through filters */
4:
5: /* This file is part of flex. */
6:
7: /* Redistribution and use in source and binary forms, with or without */
8: /* modification, are permitted provided that the following conditions */
9: /* are met: */
10:
11: /* 1. Redistributions of source code must retain the above copyright */
12: /* notice, this list of conditions and the following disclaimer. */
13: /* 2. Redistributions in binary form must reproduce the above copyright */
14: /* notice, this list of conditions and the following disclaimer in the */
15: /* documentation and/or other materials provided with the distribution. */
16:
17: /* Neither the name of the University nor the names of its contributors */
18: /* may be used to endorse or promote products derived from this software */
19: /* without specific prior written permission. */
20:
21: /* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR */
22: /* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED */
23: /* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR */
24: /* PURPOSE. */
25:
26: #include "flexdef.h"
1.4 tedu 27: static const char *check_4_gnu_m4 =
28: "m4_dnl ifdef(`__gnu__', ,"
29: "`errprint(Flex requires GNU M4. Set the PATH or set the M4 environment variable to its path name.)"
30: " m4exit(2)')\n";
1.1 tedu 31:
32:
33: /** global chain. */
34: struct filter *output_chain = NULL;
35:
36: /* Allocate and initialize an external filter.
37: * @param chain the current chain or NULL for new chain
38: * @param cmd the command to execute.
39: * @param ... a NULL terminated list of (const char*) arguments to command,
40: * not including argv[0].
41: * @return newest filter in chain
42: */
1.4 tedu 43: struct filter *
44: filter_create_ext(struct filter * chain, const char *cmd,
45: ...)
1.1 tedu 46: {
47: struct filter *f;
1.4 tedu 48: int max_args;
1.1 tedu 49: const char *s;
50: va_list ap;
51:
52: /* allocate and initialize new filter */
1.8 ! tedu 53: f = calloc(sizeof(struct filter), 1);
1.1 tedu 54: if (!f)
1.8 ! tedu 55: flexerror(_("calloc failed (f) in filter_create_ext"));
1.1 tedu 56: f->filter_func = NULL;
57: f->extra = NULL;
58: f->next = NULL;
59: f->argc = 0;
60:
61: if (chain != NULL) {
62: /* append f to end of chain */
63: while (chain->next)
64: chain = chain->next;
65: chain->next = f;
66: }
67: /* allocate argv, and populate it with the argument list. */
68: max_args = 8;
1.5 tedu 69: f->argv = malloc(sizeof(char *) * (max_args + 1));
1.1 tedu 70: if (!f->argv)
1.5 tedu 71: flexerror(_("malloc failed (f->argv) in filter_create_ext"));
1.1 tedu 72: f->argv[f->argc++] = cmd;
73:
1.4 tedu 74: va_start(ap, cmd);
75: while ((s = va_arg(ap, const char *)) != NULL) {
1.1 tedu 76: if (f->argc >= max_args) {
77: max_args += 8;
1.5 tedu 78: f->argv = realloc(f->argv,
1.4 tedu 79: sizeof(char *) * (max_args + 1));
1.1 tedu 80: }
81: f->argv[f->argc++] = s;
82: }
83: f->argv[f->argc] = NULL;
84:
1.4 tedu 85: va_end(ap);
1.1 tedu 86: return f;
87: }
88:
89: /* Allocate and initialize an internal filter.
90: * @param chain the current chain or NULL for new chain
91: * @param filter_func The function that will perform the filtering.
92: * filter_func should return 0 if successful, and -1
93: * if an error occurs -- or it can simply exit().
94: * @param extra optional user-defined data to pass to the filter.
95: * @return newest filter in chain
96: */
1.4 tedu 97: struct filter *
98: filter_create_int(struct filter * chain,
99: int (*filter_func) (struct filter *),
100: void *extra)
1.1 tedu 101: {
102: struct filter *f;
103:
104: /* allocate and initialize new filter */
1.8 ! tedu 105: f = calloc(sizeof(struct filter), 1);
1.1 tedu 106: if (!f)
1.8 ! tedu 107: flexerror(_("calloc failed in filter_create_int"));
1.1 tedu 108: f->next = NULL;
109: f->argc = 0;
110: f->argv = NULL;
111:
112: f->filter_func = filter_func;
113: f->extra = extra;
114:
115: if (chain != NULL) {
116: /* append f to end of chain */
117: while (chain->next)
118: chain = chain->next;
119: chain->next = f;
120: }
121: return f;
122: }
123:
124: /** Fork and exec entire filter chain.
125: * @param chain The head of the chain.
126: * @return true on success.
127: */
1.4 tedu 128: bool
129: filter_apply_chain(struct filter * chain)
1.1 tedu 130: {
1.4 tedu 131: int pid, pipes[2];
1.1 tedu 132:
1.4 tedu 133: /*
134: * Tricky recursion, since we want to begin the chain at the END.
135: * Why? Because we need all the forked processes to be children of
136: * the main flex process.
1.1 tedu 137: */
138: if (chain)
1.4 tedu 139: filter_apply_chain(chain->next);
1.1 tedu 140: else
141: return true;
142:
1.4 tedu 143: /*
144: * Now we are the right-most unprocessed link in the chain.
1.1 tedu 145: */
146:
1.4 tedu 147: fflush(stdout);
148: fflush(stderr);
1.1 tedu 149:
150:
1.4 tedu 151: if (pipe(pipes) == -1)
152: flexerror(_("pipe failed"));
1.1 tedu 153:
1.4 tedu 154: if ((pid = fork()) == -1)
155: flexerror(_("fork failed"));
1.1 tedu 156:
157: if (pid == 0) {
158: /* child */
159:
1.4 tedu 160: /*
161: * We need stdin (the FILE* stdin) to connect to this new
162: * pipe. There is no portable way to set stdin to a new file
163: * descriptor, as stdin is not an lvalue on some systems
164: * (BSD). So we dup the new pipe onto the stdin descriptor
165: * and use a no-op fseek to sync the stream. This is a Hail
166: * Mary situation. It seems to work.
167: */
168: close(pipes[1]);
1.3 tedu 169: clearerr(stdin);
1.4 tedu 170: if (dup2(pipes[0], fileno(stdin)) == -1)
171: flexfatal(_("dup2(pipes[0],0)"));
172: close(pipes[0]);
173: fseek(stdin, 0, SEEK_CUR);
1.1 tedu 174:
175: /* run as a filter, either internally or by exec */
176: if (chain->filter_func) {
1.4 tedu 177: int r;
1.1 tedu 178:
1.4 tedu 179: if ((r = chain->filter_func(chain)) == -1)
180: flexfatal(_("filter_func failed"));
181: exit(0);
182: } else {
183: execvp(chain->argv[0],
184: (char **const) (chain->argv));
185: lerrsf_fatal(_("exec of %s failed"),
1.3 tedu 186: chain->argv[0]);
1.1 tedu 187: }
188:
1.4 tedu 189: exit(1);
1.1 tedu 190: }
191: /* Parent */
1.4 tedu 192: close(pipes[0]);
193: if (dup2(pipes[1], fileno(stdout)) == -1)
194: flexfatal(_("dup2(pipes[1],1)"));
195: close(pipes[1]);
196: fseek(stdout, 0, SEEK_CUR);
1.1 tedu 197:
198: return true;
199: }
200:
201: /** Truncate the chain to max_len number of filters.
202: * @param chain the current chain.
203: * @param max_len the maximum length of the chain.
204: * @return the resulting length of the chain.
205: */
1.4 tedu 206: int
207: filter_truncate(struct filter * chain, int max_len)
1.1 tedu 208: {
1.4 tedu 209: int len = 1;
1.1 tedu 210:
211: if (!chain)
212: return 0;
213:
214: while (chain->next && len < max_len) {
215: chain = chain->next;
216: ++len;
217: }
218:
219: chain->next = NULL;
220: return len;
221: }
222:
223: /** Splits the chain in order to write to a header file.
224: * Similar in spirit to the 'tee' program.
225: * The header file name is in extra.
226: * @return 0 (zero) on success, and -1 on failure.
227: */
1.4 tedu 228: int
229: filter_tee_header(struct filter * chain)
1.1 tedu 230: {
1.4 tedu 231: /*
232: * This function reads from stdin and writes to both the C file and
233: * the header file at the same time.
1.1 tedu 234: */
235:
236: const int readsz = 512;
1.4 tedu 237: char *buf;
238: int to_cfd = -1;
239: FILE *to_c = NULL, *to_h = NULL;
240: bool write_header;
1.1 tedu 241:
242: write_header = (chain->extra != NULL);
243:
1.4 tedu 244: /*
245: * Store a copy of the stdout pipe, which is already piped to C file
1.1 tedu 246: * through the running chain. Then create a new pipe to the H file as
247: * stdout, and fork the rest of the chain again.
248: */
249:
1.4 tedu 250: if ((to_cfd = dup(1)) == -1)
251: flexfatal(_("dup(1) failed"));
252: to_c = fdopen(to_cfd, "w");
1.1 tedu 253:
254: if (write_header) {
1.4 tedu 255: if (freopen((char *) chain->extra, "w", stdout) == NULL)
256: flexfatal(_("freopen(headerfilename) failed"));
1.1 tedu 257:
1.4 tedu 258: filter_apply_chain(chain->next);
1.1 tedu 259: to_h = stdout;
260: }
1.4 tedu 261: /*
262: * Now to_c is a pipe to the C branch, and to_h is a pipe to the H
263: * branch.
1.1 tedu 264: */
265:
266: if (write_header) {
1.4 tedu 267: fputs(check_4_gnu_m4, to_h);
268: fputs("m4_changecom`'m4_dnl\n", to_h);
269: fputs("m4_changequote`'m4_dnl\n", to_h);
270: fputs("m4_changequote([[,]])[[]]m4_dnl\n", to_h);
271: fputs("m4_define([[M4_YY_NOOP]])[[]]m4_dnl\n", to_h);
272: fputs("m4_define( [[M4_YY_IN_HEADER]],[[]])m4_dnl\n",
273: to_h);
274: fprintf(to_h, "#ifndef %sHEADER_H\n", prefix);
275: fprintf(to_h, "#define %sHEADER_H 1\n", prefix);
276: fprintf(to_h, "#define %sIN_HEADER 1\n\n", prefix);
277: fprintf(to_h,
278: "m4_define( [[M4_YY_OUTFILE_NAME]],[[%s]])m4_dnl\n",
279: headerfilename ? headerfilename : "<stdout>");
1.1 tedu 280:
281: }
1.4 tedu 282: fputs(check_4_gnu_m4, to_c);
283: fputs("m4_changecom`'m4_dnl\n", to_c);
284: fputs("m4_changequote`'m4_dnl\n", to_c);
285: fputs("m4_changequote([[,]])[[]]m4_dnl\n", to_c);
286: fputs("m4_define([[M4_YY_NOOP]])[[]]m4_dnl\n", to_c);
287: fprintf(to_c, "m4_define( [[M4_YY_OUTFILE_NAME]],[[%s]])m4_dnl\n",
288: outfilename ? outfilename : "<stdout>");
1.1 tedu 289:
1.6 tedu 290: buf = malloc(readsz);
1.1 tedu 291: if (!buf)
1.5 tedu 292: flexerror(_("malloc failed in filter_tee_header"));
1.4 tedu 293: while (fgets(buf, readsz, stdin)) {
294: fputs(buf, to_c);
1.1 tedu 295: if (write_header)
1.4 tedu 296: fputs(buf, to_h);
1.1 tedu 297: }
298:
299: if (write_header) {
1.4 tedu 300: fprintf(to_h, "\n");
1.1 tedu 301:
1.4 tedu 302: /*
303: * write a fake line number. It will get fixed by the linedir
304: * filter.
305: */
306: fprintf(to_h, "#line 4000 \"M4_YY_OUTFILE_NAME\"\n");
307:
308: fprintf(to_h, "#undef %sIN_HEADER\n", prefix);
309: fprintf(to_h, "#endif /* %sHEADER_H */\n", prefix);
310: fputs("m4_undefine( [[M4_YY_IN_HEADER]])m4_dnl\n", to_h);
311:
312: fflush(to_h);
313: if (ferror(to_h))
314: lerrsf(_("error writing output file %s"),
315: (char *) chain->extra);
316:
317: else if (fclose(to_h))
318: lerrsf(_("error closing output file %s"),
319: (char *) chain->extra);
1.1 tedu 320: }
1.4 tedu 321: fflush(to_c);
322: if (ferror(to_c))
323: lerrsf(_("error writing output file %s"),
324: outfilename ? outfilename : "<stdout>");
325:
326: else if (fclose(to_c))
327: lerrsf(_("error closing output file %s"),
328: outfilename ? outfilename : "<stdout>");
1.1 tedu 329:
1.4 tedu 330: while (wait(0) > 0);
1.1 tedu 331:
1.4 tedu 332: exit(0);
1.1 tedu 333: return 0;
334: }
335:
336: /** Adjust the line numbers in the #line directives of the generated scanner.
337: * After the m4 expansion, the line numbers are incorrect since the m4 macros
338: * can add or remove lines. This only adjusts line numbers for generated code,
339: * not user code. This also happens to be a good place to squeeze multiple
340: * blank lines into a single blank line.
341: */
1.4 tedu 342: int
343: filter_fix_linedirs(struct filter * chain)
1.1 tedu 344: {
1.4 tedu 345: char *buf;
1.1 tedu 346: const int readsz = 512;
1.4 tedu 347: int lineno = 1;
348: bool in_gen = true; /* in generated code */
349: bool last_was_blank = false;
1.1 tedu 350:
351: if (!chain)
352: return 0;
353:
1.6 tedu 354: buf = malloc(readsz);
1.1 tedu 355: if (!buf)
1.5 tedu 356: flexerror(_("malloc failed in filter_fix_linedirs"));
1.1 tedu 357:
1.4 tedu 358: while (fgets(buf, readsz, stdin)) {
1.1 tedu 359:
360: regmatch_t m[10];
361:
362: /* Check for #line directive. */
363: if (buf[0] == '#'
1.4 tedu 364: && regexec(®ex_linedir, buf, 3, m, 0) == 0) {
1.1 tedu 365:
1.4 tedu 366: int num;
367: char *fname;
1.1 tedu 368:
369: /* extract the line number and filename */
1.4 tedu 370: num = regmatch_strtol(&m[1], buf, NULL, 0);
371: fname = regmatch_dup(&m[2], buf);
1.1 tedu 372:
1.4 tedu 373: if (strcmp(fname,
374: outfilename ? outfilename : "<stdout>") == 0 ||
375: strcmp(fname, headerfilename ? headerfilename :
376: "<stdout>") == 0) {
1.1 tedu 377:
1.4 tedu 378: char *s1, *s2;
379: char filename[MAXLINE];
1.1 tedu 380:
381: s1 = fname;
382: s2 = filename;
383:
384: while ((s2 - filename) < (MAXLINE - 1) && *s1) {
385: /* Escape the backslash */
386: if (*s1 == '\\')
387: *s2++ = '\\';
388: /* Escape the double quote */
389: if (*s1 == '\"')
390: *s2++ = '\\';
391: /* Copy the character as usual */
392: *s2++ = *s1++;
393: }
394:
395: *s2 = '\0';
396:
397: /* Adjust the line directives. */
398: in_gen = true;
1.4 tedu 399: snprintf(buf, readsz, "#line %d \"%s\"\n",
400: lineno + 1, filename);
401: } else {
402: /*
403: * it's a #line directive for code we didn't
404: * write
405: */
1.1 tedu 406: in_gen = false;
407: }
408:
1.4 tedu 409: free(fname);
1.1 tedu 410: last_was_blank = false;
411: }
412: /* squeeze blank lines from generated code */
1.3 tedu 413: else if (in_gen &&
1.4 tedu 414: regexec(®ex_blank_line, buf, 0, NULL, 0) == 0) {
1.1 tedu 415: if (last_was_blank)
416: continue;
417: else
418: last_was_blank = true;
1.4 tedu 419: } else {
1.1 tedu 420: /* it's a line of normal, non-empty code. */
421: last_was_blank = false;
422: }
423:
1.4 tedu 424: fputs(buf, stdout);
1.1 tedu 425: lineno++;
426: }
1.4 tedu 427: fflush(stdout);
428: if (ferror(stdout))
429: lerrsf(_("error writing output file %s"),
430: outfilename ? outfilename : "<stdout>");
431:
432: else if (fclose(stdout))
433: lerrsf(_("error closing output file %s"),
434: outfilename ? outfilename : "<stdout>");
1.1 tedu 435:
436: return 0;
437: }