|
|
1.1 root 1: /*
2: * To compile use
3: * cc cgrep.c -lmisc
4: * This uses libmisc.a and the misc.h header from libmisc.a.Z
5: */
6:
7: /*
8: * cgrep is egrep for c source programs.
9: * cgrep [-r new] [-clnsA] [pattern] [file ...]
10: *
11: * cgrep checks all c identifiers (cgrep considers if, etc to be
12: * identifiers) against an egrep type pattern for a full match.
13: *
14: * cgrep tmp *.c
15: * will find tmp as an identifier, but not tmpname, or tmp in a string or
16: * comment.
17: * cgrep "x|abc|d" *.c
18: * will find x, ab, or d. Note these are egrep style patterns with a
19: * surrounding ^( )$ Thus "reg*" will not match register but "reg.*"
20: * will.
21: *
22: * cgrep accumulates names with included . and -> for testing against the
23: * egrep pattern. Thus you can look for ptr->val with
24: * cgrep "ptr->val" x.c
25: * This will find ptr->val even if it contains spaces, comments or is
26: * spread across lines. If it is spread across lines it will be reported
27: * on the line containing the last token unless the -A option is used
28: * in which case it will be reported on the line containing the first
29: * token.
30: *
31: * For structure.member use
32: * cgrep "structure\.member" x.c
33: * because . has the egrep meaning any character. Do not include spaces
34: * in any pattern. Only identifiers and . or -> between identifiers are
35: * included in the tokens checked for pattern match.
36: *
37: * Where no files are given cgrep will read stdin. Options -lA require
38: * a file name. Where stdin is used with option -r a new file is written
39: * out stdout.
40: *
41: * Options.
42: *
43: * -l lists the files with hits not the lines.
44: *
45: * -n puts a line number on found lines.
46: *
47: * -s List all strings. This form takes no pattern.
48: *
49: * -c List all comments. This form takes no pattern.
50: *
51: * -A builds a tmp file and calls 'me' to process the file with the tmp file
52: * as an "error" list like the -A option of cc. Each line of this list
53: * shows the found pattern and where multiple patterns are found on a
54: * line there are multiple lines, just like cc -A showing multiple
55: * errors on a line. This is to allow emacs scripts to make systematic
56: * changes. Where a patttern is split across multiple lines -A causes
57: * it to be reported on the line with the first token, also to
58: * simplify emacs scripts.
59: *
60: * If cgrep -A is used to process a number of files it will search each
61: * file for patttern matches and call 'me' only if the file has a hit. To
62: * stop the process exit 'me' with <ctl-u><ctl-x><ctl-c>. See the new emacs
63: * initialization macro feature for a way to process these files
64: * automatically.
65: *
66: * -r Replaces all occurances of the pattern with "new". This form only matches
67: * simple tokens, not things like "ptr->val". -r is incompatible with all
68: * other options.
69: */
70: #include <ctype.h>
71: #include <stdio.h>
72: #include <misc.h>
73:
74: /*
75: * Cgrep never runs out of room on lines or buffers until malloc fails
76: * these are buffer expanders.
77: */
78: #define TROOM(buf, has, needs) while ((needs) >= has) \
79: if (NULL == (buf = realloc(buf, sizeof(*buf) * (has += 10)))) \
80: fatal(outSpace)
81:
82: #define ROOM(buf, has, needs) while ((needs) >= has) \
83: if (NULL == (buf = realloc(buf, has += 512))) \
84: fatal(outSpace)
85:
86: extern char *realloc();
87: static char outSpace[] = "cgrep: out of space";
88:
89: struct token { /* collected token array */
90: int start; /* token index on buff */
91: int atline; /* line number where token spotted */
92: };
93:
94: enum fstate { /* lexical processing state */
95: start,
96: slash, /* slash encountered in normal state */
97: comment, /* in comment */
98: star, /* * in comment */
99: bsl, /* back slash */
100: dquote, /* double quote */
101: squote, /* single quote */
102: token, /* c word */
103: minus /* - maybe -> */
104: };
105:
106: enum wstate { /* word processing states */
107: word, /* some c identifier */
108: dot, /* . or -> */
109: other /* anything else */
110: };
111:
112: /* cgrep has no fixed size arrays only growable buffers */
113: static char *buff = NULL; /* accumulate stuff */
114: static int buffLen; /* buffer length */
115:
116: static struct token *tokens = NULL; /* token array */
117: static int tokenLen; /* size of token array */
118:
119: static char *line; /* expandable input line */
120: static int lineLen; /* current length of input line */
121:
122: static char lswitch; /* list files found */
123: static char aswitch; /* call emacs with line list */
124: static char nswitch; /* print line number */
125: static char sswitch; /* print all strings */
126: static char cswitch; /* print all comments */
127: static char rswitch; /* replace found pattern */
128:
129: static regexp *pat; /* a compiled regular expression */
130:
131: static char *newstr; /* The new string with rswitch */
132:
133: static char *filen = NULL; /* the file currently being processed */
134: static char *tname = NULL; /* temp file name */
135: static FILE *tfp; /* tmp file pointer */
136:
137: static int lineno; /* current line number */
138: static int marked; /* 1 if pattern found on line. */
139:
140: /*
141: * Character types table
142: * for the ASCII character set modified to see _ as alpha.
143: * _ctype[0] is for EOF, the rest if indexed
144: * by the ascii values of the characters.
145: */
146: static unsigned char _ctype[] = {
147: 0, /* EOF */
148: _C, _C, _C, _C, _C, _C, _C, _C,
149: _C, _S|_C, _S|_C, _S|_C, _S|_C, _S|_C, _C, _C,
150: _C, _C, _C, _C, _C, _C, _C, _C,
151: _C, _C, _C, _C, _C, _C, _C, _C,
152: _S|_X, _P, _P, _P, _P, _P, _P, _P,
153: _P, _P, _P, _P, _P, _P, _P, _P,
154: _D, _D, _D, _D, _D, _D, _D, _D,
155: _D, _D, _P, _P, _P, _P, _P, _P,
156: _P, _U, _U, _U, _U, _U, _U, _U,
157: _U, _U, _U, _U, _U, _U, _U, _U,
158: _U, _U, _U, _U, _U, _U, _U, _U,
159: _U, _U, _U, _P, _P, _P, _P, _L,
160: _P, _L, _L, _L, _L, _L, _L, _L,
161: _L, _L, _L, _L, _L, _L, _L, _L,
162: _L, _L, _L, _L, _L, _L, _L, _L,
163: _L, _L, _L, _P, _P, _P, _P, _C,
164: _C, _C, _C, _C, _C, _C, _C, _C,
165: _C, _C, _C, _C, _C, _C, _C, _C,
166: _C, _C, _C, _C, _C, _C, _C, _C,
167: _C, _C, _C, _C, _C, _C, _C, _C,
168: _C, _C, _C, _C, _C, _C, _C, _C,
169: _C, _C, _C, _C, _C, _C, _C, _C,
170: _C, _C, _C, _C, _C, _C, _C, _C,
171: _C, _C, _C, _C, _C, _C, _C, _C,
172: _C, _C, _C, _C, _C, _C, _C, _C,
173: _C, _C, _C, _C, _C, _C, _C, _C,
174: _C, _C, _C, _C, _C, _C, _C, _C,
175: _C, _C, _C, _C, _C, _C, _C, _C,
176: _C, _C, _C, _C, _C, _C, _C, _C,
177: _C, _C, _C, _C, _C, _C, _C, _C,
178: _C, _C, _C, _C, _C, _C, _C, _C,
179: _C, _C, _C, _C, _C, _C, _C, _C
180: };
181:
182: /*
183: * Report errors for public domain regexp package.
184: */
185: void
186: regerror(s)
187: char *s;
188: {
189: fatal("cgrep: pattern error %s\n", s);
190: }
191:
192: /*
193: * Pattern found with -A mode. It is assumed that users of
194: * this mode will want to know about all hits on a line.
195: */
196: static void
197: emacsLine(found, atline)
198: char *found;
199: {
200: extern char *tempnam();
201:
202: /* if tmp file not opened open it. */
203: if ((NULL == tname) &&
204: ((NULL == (tname = tempnam(NULL, "cgr"))) ||
205: (NULL == (tfp = fopen(tname, "w")))))
206: fatal("cgrep: Cannot open tmp file");
207: fprintf(tfp, "%d: %s: found '%s'\n", atline, filen, found);
208: }
209:
210: /*
211: * When we get a word, dot, arrow or other we come here.
212: *
213: * cgrep accumulates things like ptr->memb.x in buff.
214: * as these are accumulated it is nessisary to match buff
215: * against the regular expression one slice at a time
216: * that is when the x in ptr->memb.x was found cgrep would check
217: * ptr->memb.x then memb.x then x
218: * against the pattern. Thus memb.x matches ptr->memb.x
219: */
220: static void
221: gota(got, what)
222: register enum wstate got; /* what we have */
223: char *what; /* the string we have or NULL */
224: {
225: static int tokenCt; /* number of tokens */
226: static enum wstate state; /* current word processing state */
227: static int wlen, blen; /* strlen(what) and strlen(buff) + 1 */
228: int i;
229:
230: if (sswitch || cswitch)
231: return;
232:
233: if (rswitch) { /* replace mode works on tokens only */
234: marked = (word == got) && regexec(pat, what);
235: return;
236: }
237:
238: switch (got) {
239: case word:
240: wlen = strlen(what);
241: switch (state) {
242: case other:
243: case word:
244: /* store start and line number of token */
245: tokenCt = 1;
246: TROOM(tokens, tokenLen, tokenCt);
247: tokens[0].start = 0;
248: tokens[0].atline = lineno;
249:
250: /* store token */
251: blen = wlen;
252: ROOM(buff, buffLen, blen);
253: strcpy(buff, what);
254: break;
255: case dot:
256: /* store start and line number of token */
257: TROOM(tokens, tokenLen, tokenCt);
258: tokens[tokenCt].start = blen;
259: tokens[tokenCt++].atline = lineno;
260:
261: /* store token */
262: blen += wlen;
263: ROOM(buff, buffLen, blen);
264: strcat(buff, what);
265: }
266:
267: /* Check the accumulated token for matches */
268: for (i = 0; i < tokenCt; i++) {
269: char *p;
270:
271: if (regexec(pat, p = buff + tokens[i].start)) {
272: if (aswitch)
273: emacsLine(p, tokens[i].atline);
274: else {
275: marked = 1;
276: break;
277: }
278: }
279: }
280:
281: state = got;
282: break;
283:
284: case dot:
285: if (word == state) {
286: blen += strlen(what);
287: ROOM(buff, buffLen, blen);
288: strcat(buff, what);
289: state = got;
290: break;
291: }
292: case other:
293: state = other;
294: }
295: }
296:
297: /*
298: * call emacs with file and tmpfile.
299: */
300: static void
301: callEmacs()
302: {
303: #ifdef GEMDOS
304: #include <path.h>
305: extern char *path(), *getenv();
306: extern char **environ;
307: static char* cmda[5] = { NULL, "-e", NULL, NULL, NULL };
308: #endif
309: int quit;
310:
311: fclose(tfp);
312: #ifdef MSDOS
313: sprintf(line, "-e %s %s", tname, filen);
314: if (0x7f == (quit = execall("me", line)))
315: #endif
316: #ifdef COHERENT
317: sprintf(line, "me -e %s %s ", tname, filen);
318: if (0x7f == (quit = system(line)))
319: #endif
320: #ifdef GEMDOS
321: cmda[2] = tname;
322: cmda[3] = filen;
323: if ((NULL == cmda[0]) &&
324: (NULL == (cmda[0] = path(getenv("PATH"), "me.tos", 1))))
325: fatal("cgrep: Cannot locate me.tos\n");
326: else if ((quit = execve(cmda[0], cmda, environ)) < 0)
327: #endif
328: fatal("cgrep: cannot execute 'me'");
329: unlink(tname);
330: free(tname);
331: tname = NULL;
332: if (quit)
333: exit(0);
334: }
335:
336: /*
337: * print a hit for options -s or -c.
338: */
339: static void
340: printx(s)
341: char *s;
342: {
343: if (aswitch)
344: emacsLine(s, lineno);
345: else {
346: if (NULL != filen)
347: printf("%s: ", filen);
348: if (nswitch)
349: printf("%4d: ", lineno);
350: printf("%s\n", s);
351: }
352: }
353:
354: /*
355: * Lexically process a file.
356: */
357: static void
358: lex()
359: {
360: int c, i;
361: enum fstate state, pstate;
362: char *w, changed;
363: FILE *ifp, *tfp;
364:
365: if (NULL == filen)
366: ifp = stdin;
367: else if (NULL == (ifp = fopen(filen, "r"))) {
368: fprintf(stderr, "cgrep: warning cannot open %s\n", filen);
369: return;
370: }
371:
372: if (rswitch) {
373: changed = 0; /* no changes so far */
374:
375: if (NULL == filen)
376: tfp = stdout;
377: else if ((NULL == (tname = tempnam(NULL, "cse"))) ||
378: (NULL == (tfp = fopen(tname, "w"))))
379: fatal("csed: Cannot open tmp file");
380: }
381:
382: lineno = 1;
383: i = marked = 0;
384: gota(other, NULL); /* initialize word machine */
385: for (state = start; ; ) {
386: line[i] = '\0';
387: c = fgetc(ifp);
388:
389: switch (state) {
390: case minus:
391: if ('>' == c) {
392: gota(dot, "->");
393: state = start;
394: break;
395: }
396: goto isstart;
397: case token:
398: if (isalnum(c))
399: break;
400: gota(word, w);
401:
402: /* we have a word to replace */
403: if (rswitch && marked) {
404: i += strlen(newstr) - strlen(w);
405: ROOM(line, lineLen, i);
406: strcpy(w, newstr);
407: changed = 1;
408: }
409: isstart: state = start;
410: case start:
411: switch (c) {
412: case '.':
413: gota(dot, ".");
414: break;
415: case '-':
416: state = minus;
417: break;
418: case '/':
419: state = slash;
420: break;
421: case '\\':
422: pstate = state;
423: state = bsl;
424: break;
425: case '"':
426: w = line + i;
427: state = dquote;
428: break;
429: case '\'':
430: state = squote;
431: break;
432: default:
433: if (isalpha(c)) {
434: w = line + i;
435: state = token;
436: }
437: else if (!isspace(c))
438: gota(other, NULL);
439: }
440: break;
441: case slash:
442: if ('*' != c)
443: goto isstart;
444: w = line + i + 1;
445: state = comment;
446: break;
447: case star:
448: if ('/' == c) {
449: if (cswitch) { /* report comment */
450: line[i - 1] = '\0';
451: printx(w);
452: line[i - 1] = '*';
453: }
454: state = start;
455: break;
456: }
457: state = comment;
458: case comment:
459: if ('*' == c)
460: state = star;
461: break;
462: case bsl:
463: state = pstate;
464: break;
465: case dquote:
466: switch (c) {
467: case '"':
468: case '\n':
469: state = start;
470: if (sswitch)
471: printx(w + 1);
472: else
473: gota(other, NULL);
474: break;
475: case '\\':
476: pstate = state;
477: state = bsl;
478: break;
479: }
480: break;
481: case squote:
482: switch (c) {
483: case '\'':
484: case '\n':
485: gota(other, NULL);
486: state = start;
487: break;
488: case '\\':
489: pstate = state;
490: state = bsl;
491: break;
492: }
493: break;
494: }
495: if (('\n' != c) && (EOF != c)) {
496: line[i++] = c;
497: ROOM(line, lineLen, i);
498: }
499: else { /* end of line */
500: if (rswitch) {
501: if (EOF == c) {
502: if (!i) /* null line */
503: break;
504: }
505: else
506: line[i++] = c;
507: ROOM(line, lineLen, i);
508: line[i] = 0;
509: fputs(line, tfp);
510: marked = 0;
511: }
512:
513: if (cswitch && (comment == state)) {
514: printx(w);
515: w = line;
516: }
517:
518: if (marked) {
519: marked = 0;
520: if (lswitch) {
521: printf("%s\n", filen);
522: break;
523: }
524: printx(line);
525: }
526:
527: lineno++;
528: i = 0;
529: if (EOF == c)
530: break;
531: }
532: }
533:
534: fclose(ifp);
535:
536: if (rswitch) {
537: fclose(tfp);
538:
539: if (NULL == filen)
540: ; /* do nothing file is already out */
541: else if (changed) {
542: unlink(filen);
543: sprintf(line, "mv %s %s", tname, filen);
544: system(line);
545: }
546: else
547: unlink(tname);
548: }
549:
550: if (aswitch && (NULL != tname)) /* tmp file opened for -A option */
551: callEmacs();
552: }
553:
554: main(argc, argv)
555: int argc;
556: register char **argv;
557: {
558: register char c;
559: int errsw = 0;
560: static char msg[] = "cgrep [-r newStr] [-clnsA] [pattern] filename ...";
561: char *p, *q;
562: extern int optind;
563: extern char *optarg;
564:
565: if (1 == argc)
566: usage(msg);
567:
568: while (EOF != (c = getopt(argc, argv, "cslnA?r:V"))) {
569: switch (c) {
570: case 'V':
571: printf("cgrep version 1.1\n");
572: exit(0);
573: case 'c':
574: cswitch = 1; /* comments only */
575: break;
576: case 's':
577: sswitch = 1; /* strings only */
578: break;
579: case 'l':
580: lswitch = 1; /* print filenames only */
581: break;
582: case 'n':
583: nswitch = 1; /* print line numbers */
584: break;
585: case 'A':
586: aswitch = 1; /* interact with emacs */
587: break;
588: case 'r':
589: rswitch = 1; /* replace hits */
590: newstr = optarg;
591: break;
592: default:
593: errsw = 1;
594: }
595: }
596:
597: /* check unknown switches and rswitch goes with no other switches */
598: if (errsw ||
599: (rswitch && (aswitch | nswitch | lswitch | cswitch | sswitch)))
600: usage(msg);
601:
602: if (!sswitch && !cswitch) { /* process pattern */
603: if (optind == argc) /* no pattern */
604: usage(msg);
605:
606: /* inclose pattern in ^( )$ to force full match */
607: p = alloc(5 + strlen(q = argv[optind++]));
608: sprintf(p, "^(%s)$", q);
609: if (NULL == (pat = regcomp(p)))
610: fatal("cgrep: Illegal pattern\n");
611: }
612:
613: ROOM(line, lineLen, 1); /* get input line started */
614:
615: if (optind == argc) {
616: if (aswitch | lswitch)
617: fatal("cgrep: -A and -l require a filename");
618: lex();
619: }
620: else {
621: while (optind < argc) {
622: filen = argv[optind++];
623: lex();
624: }
625: }
626: exit(0);
627: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.