|
|
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: * -R Replaces all occurances of the pattern with a new after the following
71: * substitutions. & is the whole pattern found. \1 is the first
72: * parenthesized subexpression, \2 the second etc.
73: *
74: * -d token. Take following string and comment. For documentation extraction.
75: * use the first char of the token as an output delimeter.
76: *
77: * -C token. Use token as delimeter -c option.
78: */
79: #include <ctype.h>
80: #include <stdio.h>
81: #include <misc.h>
82:
83: /*
84: * Cgrep never runs out of room on lines or buffers until malloc fails
85: * these are buffer expanders.
86: */
87: #define TROOM(buf, has, needs) while ((needs) >= has) \
88: if (NULL == (buf = realloc(buf, sizeof(*buf) * (has += 10)))) \
89: fatal(outSpace)
90:
91: #define ROOM(buf, has, needs) while ((needs) >= has) \
92: if (NULL == (buf = realloc(buf, has += 512))) \
93: fatal(outSpace)
94:
95: extern char *realloc();
96: static char outSpace[] = "cgrep: out of space";
97:
98: struct token { /* collected token array */
99: int start; /* token index on buff */
100: int atline; /* line number where token spotted */
101: };
102:
103: enum fstate { /* lexical processing state */
104: start,
105: slash, /* slash encountered in normal state */
106: comment, /* in comment */
107: cppcom, /* in cpp comment */
108: star, /* * in comment */
109: bsl, /* back slash */
110: dquote, /* double quote */
111: squote, /* single quote */
112: token, /* c word */
113: minus /* - maybe -> */
114: };
115:
116: enum wstate { /* word processing states */
117: word, /* some c identifier */
118: dot, /* . or -> */
119: other /* anything else */
120: };
121:
122: /* cgrep has no fixed size arrays only growable buffers */
123: static char *buff = NULL; /* accumulate stuff */
124: static int buffLen; /* buffer length */
125:
126: static struct token *tokens = NULL; /* token array */
127: static int tokenLen; /* size of token array */
128:
129: static char *line; /* expandable input line */
130: static int lineLen; /* current length of input line */
131:
132: static char lswitch; /* list files found */
133: static char aswitch; /* call emacs with line list */
134: static char dswitch; /* special for docs */
135: static char dtoken[80]; /* token copy for dswitch */
136: static char nswitch; /* print line number */
137: static char sswitch; /* print all strings */
138: static char cswitch; /* print all comments */
139: static char rswitch; /* replace found pattern */
140: static char Rswitch; /* do substition magic */
141:
142: static regexp *pat; /* a compiled regular expression */
143:
144: static char *newstr; /* The new string with rswitch */
145:
146: static char *filen = NULL; /* the file currently being processed */
147: static char *tname = NULL; /* temp file name */
148: static FILE *tfp; /* tmp file pointer */
149:
150: static int lineno; /* current line number */
151: static int marked; /* 1 if pattern found on line. */
152:
153: static int matched; /* 1 if any match found. */
154:
155: /*
156: * Character types table
157: * for the ASCII character set modified to see _ as alpha.
158: * _ctype[0] is for EOF, the rest if indexed
159: * by the ascii values of the characters.
160: */
161: static unsigned char _ctype[] = {
162: 0, /* EOF */
163: _C, _C, _C, _C, _C, _C, _C, _C,
164: _C, _S|_C, _S|_C, _S|_C, _S|_C, _S|_C, _C, _C,
165: _C, _C, _C, _C, _C, _C, _C, _C,
166: _C, _C, _C, _C, _C, _C, _C, _C,
167: _S|_X, _P, _P, _P, _P, _P, _P, _P,
168: _P, _P, _P, _P, _P, _P, _P, _P,
169: _N, _N, _N, _N, _N, _N, _N, _N,
170: _N, _N, _P, _P, _P, _P, _P, _P,
171: _P, _U, _U, _U, _U, _U, _U, _U,
172: _U, _U, _U, _U, _U, _U, _U, _U,
173: _U, _U, _U, _U, _U, _U, _U, _U,
174: _U, _U, _U, _P, _P, _P, _P, _L,
175: _P, _L, _L, _L, _L, _L, _L, _L,
176: _L, _L, _L, _L, _L, _L, _L, _L,
177: _L, _L, _L, _L, _L, _L, _L, _L,
178: _L, _L, _L, _P, _P, _P, _P, _C,
179: _C, _C, _C, _C, _C, _C, _C, _C,
180: _C, _C, _C, _C, _C, _C, _C, _C,
181: _C, _C, _C, _C, _C, _C, _C, _C,
182: _C, _C, _C, _C, _C, _C, _C, _C,
183: _C, _C, _C, _C, _C, _C, _C, _C,
184: _C, _C, _C, _C, _C, _C, _C, _C,
185: _C, _C, _C, _C, _C, _C, _C, _C,
186: _C, _C, _C, _C, _C, _C, _C, _C,
187: _C, _C, _C, _C, _C, _C, _C, _C,
188: _C, _C, _C, _C, _C, _C, _C, _C,
189: _C, _C, _C, _C, _C, _C, _C, _C,
190: _C, _C, _C, _C, _C, _C, _C, _C,
191: _C, _C, _C, _C, _C, _C, _C, _C,
192: _C, _C, _C, _C, _C, _C, _C, _C,
193: _C, _C, _C, _C, _C, _C, _C, _C,
194: _C, _C, _C, _C, _C, _C, _C, _C
195: };
196:
197: /*
198: * Report errors for public domain regexp package.
199: */
200: void
201: regerror(s)
202: char *s;
203: {
204: fatal("cgrep: pattern error %s\n", s);
205: }
206:
207: /*
208: * Pattern found with -A mode. It is assumed that users of
209: * this mode will want to know about all hits on a line.
210: */
211: static void
212: emacsLine(found, atline)
213: char *found;
214: {
215: extern char *tempnam();
216:
217: /* if tmp file not opened open it. */
218: if ((NULL == tname) &&
219: ((NULL == (tname = tempnam(NULL, "cgr"))) ||
220: (NULL == (tfp = fopen(tname, "w")))))
221: fatal("cgrep: Cannot open tmp file");
222: fprintf(tfp, "%d: %s: found '%s'\n", atline, filen, found);
223: }
224:
225: /*
226: * When we get a word, dot, arrow or other we come here.
227: *
228: * cgrep accumulates things like ptr->memb.x in buff.
229: * as these are accumulated it is nessisary to match buff
230: * against the regular expression one slice at a time
231: * that is when the x in ptr->memb.x was found cgrep would check
232: * ptr->memb.x then memb.x then x
233: * against the pattern. Thus memb.x matches ptr->memb.x
234: */
235: static void
236: gota(got, what)
237: register enum wstate got; /* what we have */
238: char *what; /* the string we have or NULL */
239: {
240: static int tokenCt; /* number of tokens */
241: static enum wstate state; /* current word processing state */
242: static int wlen, blen; /* strlen(what) and strlen(buff) + 1 */
243: int i;
244:
245: if (sswitch || cswitch)
246: return;
247:
248: if (rswitch) { /* replace mode works on tokens only */
249: marked = (word == got) && regexec(pat, what);
250: matched |= marked;
251: return;
252: }
253:
254: switch (got) {
255: case word:
256: wlen = strlen(what);
257: switch (state) {
258: case other:
259: case word:
260: /* store start and line number of token */
261: tokenCt = 1;
262: TROOM(tokens, tokenLen, tokenCt);
263: tokens[0].start = 0;
264: tokens[0].atline = lineno;
265:
266: /* store token */
267: blen = wlen;
268: ROOM(buff, buffLen, blen);
269: strcpy(buff, what);
270: break;
271: case dot:
272: /* store start and line number of token */
273: TROOM(tokens, tokenLen, tokenCt);
274: tokens[tokenCt].start = blen;
275: tokens[tokenCt++].atline = lineno;
276:
277: /* store token */
278: blen += wlen;
279: ROOM(buff, buffLen, blen);
280: strcat(buff, what);
281: }
282:
283: /* Check the accumulated token for matches */
284: for (i = 0; i < tokenCt; i++) {
285: char *p;
286:
287: if (regexec(pat, p = buff + tokens[i].start)) {
288: if (aswitch)
289: emacsLine(p, tokens[i].atline);
290: else {
291: marked = 1;
292: matched = 1;
293: if (dswitch) {
294: strcpy(dtoken, p);
295: cswitch = sswitch = 1;
296: }
297: break;
298: }
299: }
300: }
301:
302: state = got;
303: break;
304:
305: case dot:
306: if (word == state) {
307: blen += strlen(what);
308: ROOM(buff, buffLen, blen);
309: strcat(buff, what);
310: state = got;
311: break;
312: }
313: case other:
314: state = other;
315: }
316: }
317:
318: /*
319: * call emacs with file and tmpfile.
320: */
321: static void
322: callEmacs()
323: {
324: #ifdef GEMDOS
325: #include <path.h>
326: extern char *path(), *getenv();
327: extern char **environ;
328: static char* cmda[5] = { NULL, "-e", NULL, NULL, NULL };
329: #endif
330: int quit;
331:
332: fclose(tfp);
333: #ifdef MSDOS
334: sprintf(line, "-e %s %s", tname, filen);
335: if (0x7f == (quit = execall("me", line)))
336: #endif
337: #ifdef COHERENT
338: sprintf(line, "me -e %s %s ", tname, filen);
339: if (0x7f == (quit = system(line)))
340: #endif
341: #ifdef GEMDOS
342: cmda[2] = tname;
343: cmda[3] = filen;
344: if ((NULL == cmda[0]) &&
345: (NULL == (cmda[0] = path(getenv("PATH"), "me.tos", 1))))
346: fatal("cgrep: Cannot locate me.tos\n");
347: else if ((quit = execve(cmda[0], cmda, environ)) < 0)
348: #endif
349: fatal("cgrep: cannot execute 'me'");
350: unlink(tname);
351: free(tname);
352: tname = NULL;
353: if (quit)
354: exit(0);
355: }
356:
357: /*
358: * find first word in a string excluding printf % constructs.
359: * make it lower case and return it.
360: */
361: static char *
362: firstWord(p)
363: char *p;
364: {
365: static char buf[80];
366: char *out, c;
367: enum state { start, pct, name } state;
368:
369: out = NULL;
370: for (state = start; c = *p++;) {
371: switch(state) {
372: case start:
373: if (isalpha(c)) {
374: state = name;
375: out = buf;
376: *out++ = tolower(c);
377: continue;
378: }
379: if ('%' == c)
380: state = pct;
381: continue;
382:
383: case pct:
384: if (isalpha(c) && 'l' != c)
385: state = start;
386: continue;
387:
388: case name:
389: if (isalnum(c)) {
390: *out++ = tolower(c);
391: continue;
392: }
393: *out = '\0';
394: return (buf);
395: }
396: }
397: if (NULL == out)
398: return ("???");
399: *out = '\0';
400: return (buf);
401: }
402:
403: /*
404: * print a hit for options -s or -c. Option -d creates -s and -c.
405: */
406: static void
407: printx(s, sw)
408: register char *s;
409: {
410: register char c, *p;
411:
412: if (aswitch)
413: emacsLine(s, lineno);
414: else if (cswitch > 1) {
415: for (p = s; (c = *p) && isspace(c); p++)
416: ;
417: if ('*' == c)
418: s = p + 1;
419:
420: printf("%s", s);
421: putchar(sw ? cswitch : '\n');
422: }
423: else if (dswitch) {
424: for (p = s; (c = *p) && isspace(c); p++)
425: ;
426: if ('*' == c)
427: s = p + 1;
428:
429: if (dtoken[0]) {
430: printf("%s%c%s%c%s",
431: firstWord(s), dswitch, s, dswitch, dtoken);
432: dtoken[0] = '\0';
433: }
434: else
435: printf("%s", s);
436: putchar(cswitch ? dswitch : '\n');
437: }
438: else {
439: if (NULL != filen)
440: printf("%s: ", filen);
441: if (nswitch)
442: printf("%4d: ", lineno);
443: printf("%s\n", s);
444: }
445: }
446:
447: /*
448: * Lexically process a file.
449: */
450: static void
451: lex()
452: {
453: int c, i;
454: enum fstate state, pstate;
455: char *w, changed;
456: FILE *ifp, *tfp;
457:
458: if (NULL == filen)
459: ifp = stdin;
460: else if (NULL == (ifp = fopen(filen, "r"))) {
461: fprintf(stderr, "cgrep: warning cannot open %s\n", filen);
462: return;
463: }
464:
465: if (rswitch) {
466: changed = 0; /* no changes so far */
467:
468: if (NULL == filen)
469: tfp = stdout;
470: else if ((NULL == (tname = tempnam(NULL, "cse"))) ||
471: (NULL == (tfp = fopen(tname, "w"))))
472: fatal("cgrep: Cannot open tmp file");
473: }
474:
475: lineno = 1;
476: i = marked = 0;
477: gota(other, NULL); /* initialize word machine */
478:
479: for (state = start; ; ) {
480: line[i] = '\0';
481: c = fgetc(ifp);
482:
483: switch (state) {
484: case minus:
485: if ('>' == c) {
486: gota(dot, "->");
487: state = start;
488: break;
489: }
490: goto isstart;
491: case token:
492: if (isalnum(c))
493: break;
494: gota(word, w);
495:
496: /* we have a word to replace */
497: if (rswitch && marked) {
498: if(Rswitch) {
499: char buf[80];
500:
501: regsub(pat, newstr, buf);
502: i += strlen(buf) - strlen(w);
503: ROOM(line, lineLen, i);
504: strcpy(w, buf);
505: } else {
506: i += strlen(newstr) - strlen(w);
507: ROOM(line, lineLen, i);
508: strcpy(w, newstr);
509: }
510: changed = 1;
511: }
512: isstart: state = start;
513: case start:
514: switch (c) {
515: case '.':
516: gota(dot, ".");
517: break;
518: case '-':
519: state = minus;
520: break;
521: case '/':
522: state = slash;
523: break;
524: case '\\':
525: pstate = state;
526: state = bsl;
527: break;
528: case '"':
529: w = line + i;
530: state = dquote;
531: break;
532: case '\'':
533: state = squote;
534: break;
535: default:
536: if (isalpha(c)) {
537: w = line + i;
538: state = token;
539: }
540: else if (!isspace(c))
541: gota(other, NULL);
542: }
543: break;
544:
545: case slash:
546: switch (c) {
547: case '*':
548: state = comment;
549: w = line + i + 1;
550: break;
551: case '/':
552: state = cppcom;
553: w = line + i + 1;
554: break;
555: default:
556: goto isstart;
557: }
558: break;
559:
560: case cppcom:
561: if ('\n' == c) {
562: if (cswitch) { /* report comment */
563: if (dswitch)
564: cswitch = 0;
565: line[i] = '\0';
566: printx(w, 0);
567: line[i] = '\n';
568: }
569: state = start;
570: }
571: break;
572:
573: case star:
574: if ('/' == c) {
575: if (cswitch) { /* report comment */
576: if (dswitch)
577: cswitch = 0;
578: line[i - 1] = '\0';
579: printx(w, 0);
580: line[i - 1] = '*';
581: }
582: state = start;
583: break;
584: }
585: state = comment;
586: case comment:
587: if ('*' == c)
588: state = star;
589: break;
590: case bsl:
591: state = pstate;
592: break;
593: case dquote:
594: switch (c) {
595: case '"':
596: case '\n':
597: state = start;
598: if (sswitch) {
599: if (dswitch)
600: sswitch = 0;
601: printx(w + 1, 0);
602: }
603: else
604: gota(other, NULL);
605: break;
606: case '\\':
607: pstate = state;
608: state = bsl;
609: break;
610: }
611: break;
612: case squote:
613: switch (c) {
614: case '\'':
615: case '\n':
616: gota(other, NULL);
617: state = start;
618: break;
619: case '\\':
620: pstate = state;
621: state = bsl;
622: break;
623: }
624: break;
625: }
626: if (('\n' != c) && (EOF != c)) {
627: line[i++] = c;
628: ROOM(line, lineLen, i);
629: }
630: else { /* end of line */
631: if (rswitch) {
632: if (EOF == c) {
633: if (!i) /* null line */
634: break;
635: }
636: else
637: line[i++] = c;
638: ROOM(line, lineLen, i);
639: line[i] = 0;
640: fputs(line, tfp);
641: marked = 0;
642: }
643:
644: if (cswitch && (comment == state)) {
645: printx(w, 1);
646: w = line;
647: }
648:
649: if (marked && !dswitch) {
650: if (lswitch) {
651: printf("%s\n", filen);
652: break;
653: }
654: printx(line, 0);
655: }
656:
657: lineno++;
658: marked = i = 0;
659: if (EOF == c)
660: break;
661: }
662: }
663:
664: fclose(ifp);
665:
666: if (rswitch) {
667: fclose(tfp);
668:
669: if (NULL != filen) { /* tmp file used */
670: if (changed) {
671: unlink(filen);
672: sprintf(line, "mv %s %s", tname, filen);
673: system(line);
674: }
675: else
676: unlink(tname);
677: }
678: }
679:
680: if (aswitch && (NULL != tname)) /* tmp file opened for -A option */
681: callEmacs();
682: }
683:
684: main(argc, argv)
685: int argc;
686: register char **argv;
687: {
688: register char c;
689: int errsw = 0;
690: static char msg[] =
691: "cgrep [-[rR] newStr] [-clnsA] [pattern] filename ...";
692: char *p, *q;
693: extern int optind;
694: extern char *optarg;
695:
696: if (1 == argc)
697: usage(msg);
698:
699: while (EOF != (c = getopt(argc, argv, "cC:d:slnA?r:R:V"))) {
700: switch (c) {
701: case 'V':
702: printf("cgrep version 1.2\n");
703: exit(0);
704: case 'C':
705: cswitch = *optarg; /* get delimiter */
706: break;
707: case 'c':
708: cswitch = 1; /* comments only */
709: break;
710: case 'd': /* report following string & comment */
711: dswitch = *optarg;
712: break;
713: case 's':
714: sswitch = 1; /* strings only */
715: break;
716: case 'l':
717: lswitch = 1; /* print filenames only */
718: break;
719: case 'n':
720: nswitch = 1; /* print line numbers */
721: break;
722: case 'A':
723: aswitch = 1; /* interact with emacs */
724: break;
725: case 'R':
726: Rswitch = 1; /* do substitution magic */
727: for (p = optarg; *p; p++)
728: if('\\' == *p && (p[1] <= '8') && (p[1] > '0'))
729: p[1]++;
730: case 'r':
731: rswitch = 1; /* replace hits */
732: newstr = optarg;
733: break;
734: default:
735: errsw = 1;
736: }
737: }
738:
739: /* check unknown switches and rswitch goes with no other switches */
740: if (errsw ||
741: (rswitch && (aswitch | nswitch | lswitch | cswitch | sswitch)))
742: usage(msg);
743:
744: if (!sswitch && !cswitch) { /* process pattern */
745: if (optind == argc) /* no pattern */
746: usage(msg);
747:
748: /* inclose pattern in ^( )$ to force full match */
749: p = alloc(5 + strlen(q = argv[optind++]));
750: sprintf(p, "^(%s)$", q);
751: if (NULL == (pat = regcomp(p)))
752: fatal("cgrep: Illegal pattern\n");
753: }
754:
755: ROOM(line, lineLen, 1); /* get input line started */
756:
757: if (optind == argc) {
758: if (aswitch | lswitch)
759: fatal("cgrep: -A and -l require a filename");
760: lex();
761: }
762: else {
763: while (optind < argc) {
764: filen = argv[optind++];
765: lex();
766: }
767: }
768: exit(! matched);
769: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.