This commit was generated by cvs2svn to track changes on a CVS vendor
[unix-history] / usr.bin / vi / nex / ex_tag.c
CommitLineData
18ae9d6b
AS
1/*-
2 * Copyright (c) 1992, 1993
3 * The Regents of the University of California. All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * David Hitz of Auspex Systems, Inc.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
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 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by the University of
19 * California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#ifndef lint
38static char sccsid[] = "@(#)ex_tag.c 8.31 (Berkeley) 1/22/94";
39#endif /* not lint */
40
41#include <sys/types.h>
42#include <sys/mman.h>
43#include <sys/stat.h>
44
45#include <ctype.h>
46#include <errno.h>
47#include <fcntl.h>
48#include <stddef.h>
49#include <stdlib.h>
50#include <string.h>
51#include <unistd.h>
52
53#include "vi.h"
54#include "excmd.h"
55#include "tag.h"
56
57static char *binary_search __P((char *, char *, char *));
58static int compare __P((char *, char *, char *));
59static char *linear_search __P((char *, char *, char *));
60static int search __P((SCR *, char *, char *, char **));
61static int tag_get __P((SCR *, char *, char **, char **, char **));
62
63/*
64 * ex_tagfirst --
65 * The tag code can be entered from main, i.e. "vi -t tag".
66 */
67int
68ex_tagfirst(sp, tagarg)
69 SCR *sp;
70 char *tagarg;
71{
72 FREF *frp;
73 MARK m;
74 long tl;
75 u_int flags;
76 int sval;
77 char *p, *tag, *name, *search;
78
79 /* Taglength may limit the number of characters. */
80 if ((tl = O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(tagarg) > tl)
81 tagarg[tl] = '\0';
82
83 /* Get the tag information. */
84 if (tag_get(sp, tagarg, &tag, &name, &search))
85 return (1);
86
87 /* Create the file entry. */
88 if ((frp = file_add(sp, NULL, name, 0)) == NULL)
89 return (1);
90 if (file_init(sp, frp, NULL, 0))
91 return (1);
92
93 /*
94 * !!!
95 * Historic vi accepted a line number as well as a search
96 * string, and people are apparently still using the format.
97 */
98 if (isdigit(search[0])) {
99 m.lno = atoi(search);
100 m.cno = 0;
101 } else {
102 /*
103 * Search for the tag; cheap fallback for C functions if
104 * the name is the same but the arguments have changed.
105 */
106 m.lno = 1;
107 m.cno = 0;
108 flags = SEARCH_FILE | SEARCH_TAG | SEARCH_TERM;
109 sval = f_search(sp, sp->ep, &m, &m, search, NULL, &flags);
110 if (sval && (p = strrchr(search, '(')) != NULL) {
111 p[1] = '\0';
112 sval = f_search(sp, sp->ep,
113 &m, &m, search, NULL, &flags);
114 }
115 if (sval)
116 msgq(sp, M_ERR, "%s: search pattern not found.", tag);
117 }
118
119 /* Set up the screen. */
120 frp->lno = m.lno;
121 frp->cno = m.cno;
122 F_SET(frp, FR_CURSORSET);
123
124 /* Might as well make this the default tag. */
125 if ((EXP(sp)->tlast = strdup(tagarg)) == NULL) {
126 msgq(sp, M_SYSERR, NULL);
127 return (1);
128 }
129 return (0);
130}
131
132/*
133 * ex_tagpush -- :tag [file]
134 * Move to a new tag.
135 *
136 * The tags stacks in nvi are a bit tricky. Each tag contains a file name,
137 * search string, and line/column numbers. The search string is only used
138 * for the first access and for user display. The first record on the stack
139 * is the place where we first did a tag, so it has no search string. The
140 * second record is the first tag, and so on. Note, this means that the
141 * "current" tag is always on the stack. Each tag has a line/column which is
142 * the location from which the user tagged the following TAG entry, and which
143 * is used as the return location.
144 */
145int
146ex_tagpush(sp, ep, cmdp)
147 SCR *sp;
148 EXF *ep;
149 EXCMDARG *cmdp;
150{
151 enum {TC_CHANGE, TC_CURRENT} which;
152 EX_PRIVATE *exp;
153 FREF *frp;
154 MARK m;
155 TAG *tp;
156 u_int flags;
157 int sval;
158 long tl;
159 char *name, *p, *search, *tag;
160
161 exp = EXP(sp);
162 switch (cmdp->argc) {
163 case 1:
164 if (exp->tlast != NULL)
165 FREE(exp->tlast, strlen(exp->tlast) + 1);
166 if ((exp->tlast = strdup(cmdp->argv[0]->bp)) == NULL) {
167 msgq(sp, M_SYSERR, NULL);
168 return (1);
169 }
170 break;
171 case 0:
172 if (exp->tlast == NULL) {
173 msgq(sp, M_ERR, "No previous tag entered.");
174 return (1);
175 }
176 break;
177 default:
178 abort();
179 }
180
181 /* Taglength may limit the number of characters. */
182 if ((tl = O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(exp->tlast) > tl)
183 exp->tlast[tl] = '\0';
184
185 /* Get the tag information. */
186 if (tag_get(sp, exp->tlast, &tag, &name, &search))
187 return (1);
188
189 /* Get a new FREF structure. */
190 if ((frp = file_add(sp, sp->frp, name, 1)) == NULL) {
191 FREE(tag, strlen(tag));
192 return (1);
193 }
194
195 /*
196 * Get a tag structure -- if this is the first tag, push it on the
197 * stack as a placeholder and get another tag structure. Set the
198 * line/column of the most recent element on the stack to be the
199 * current values, including the file pointer. Then push the new
200 * TAG onto the stack with the new file and search string for user
201 * display.
202 */
203 CALLOC(sp, tp, TAG *, 1, sizeof(TAG));
204 if (tp != NULL && exp->tagq.tqh_first == NULL) {
205 TAILQ_INSERT_HEAD(&exp->tagq, tp, q);
206 CALLOC(sp, tp, TAG *, 1, sizeof(TAG));
207 }
208 if (exp->tagq.tqh_first != NULL) {
209 exp->tagq.tqh_first->frp = sp->frp;
210 exp->tagq.tqh_first->lno = sp->lno;
211 exp->tagq.tqh_first->cno = sp->cno;
212 }
213 if (tp != NULL) {
214 if ((tp->search = strdup(search)) == NULL)
215 msgq(sp, M_SYSERR, NULL);
216 else
217 tp->slen = strlen(search);
218 tp->frp = frp;
219 TAILQ_INSERT_HEAD(&exp->tagq, tp, q);
220 }
221
222 /* Switch to the new file. */
223 if (sp->frp == frp)
224 which = TC_CURRENT;
225 else {
226 MODIFY_CHECK(sp, sp->ep, F_ISSET(cmdp, E_FORCE));
227
228 if (file_init(sp, frp, NULL, 0)) {
229 if (tp != NULL)
230 FREE(tp, sizeof(TAG));
231 FREE(tag, strlen(tag));
232 return (1);
233 }
234 which = TC_CHANGE;
235 }
236
237 /*
238 * !!!
239 * Historic vi accepted a line number as well as a search
240 * string, and people are apparently still using the format.
241 */
242 if (isdigit(search[0])) {
243 m.lno = atoi(search);
244 m.cno = 0;
245 sval = 0;
246 } else {
247 /*
248 * Search for the tag; cheap fallback for C functions
249 * if the name is the same but the arguments have changed.
250 */
251 m.lno = 1;
252 m.cno = 0;
253 flags = SEARCH_FILE | SEARCH_TAG | SEARCH_TERM;
254 sval = f_search(sp, sp->ep, &m, &m, search, NULL, &flags);
255 if (sval && (p = strrchr(search, '(')) != NULL) {
256 p[1] = '\0';
257 sval = f_search(sp, sp->ep,
258 &m, &m, search, NULL, &flags);
259 p[1] = '(';
260 }
261 if (sval)
262 msgq(sp, M_ERR, "%s: search pattern not found.", tag);
263 }
264 free(tag);
265
266 switch (which) {
267 case TC_CHANGE:
268 frp->lno = m.lno;
269 frp->cno = m.cno;
270 F_SET(frp, FR_CURSORSET);
271 F_SET(sp, S_FSWITCH);
272 break;
273 case TC_CURRENT:
274 if (sval)
275 return (1);
276 sp->lno = m.lno;
277 sp->cno = m.cno;
278 break;
279 }
280 return (0);
281}
282
283/* Free a tag or tagf structure from a queue. */
284#define FREETAG(tp) { \
285 TAILQ_REMOVE(&exp->tagq, (tp), q); \
286 if ((tp)->search != NULL) \
287 free((tp)->search); \
288 FREE((tp), sizeof(TAGF)); \
289}
290#define FREETAGF(tfp) { \
291 TAILQ_REMOVE(&exp->tagfq, (tfp), q); \
292 free((tfp)->name); \
293 FREE((tfp), sizeof(TAGF)); \
294}
295
296/*
297 * ex_tagpop -- :tagp[op][!] [number | file]
298 * Pop the tag stack.
299 */
300int
301ex_tagpop(sp, ep, cmdp)
302 SCR *sp;
303 EXF *ep;
304 EXCMDARG *cmdp;
305{
306 EX_PRIVATE *exp;
307 TAG *ntp, *tp;
308 recno_t lno;
309 long off, saved_off;
310 size_t arglen, cno;
311 char *arg, *p, *t;
312
313 /* Check for an empty stack. */
314 exp = EXP(sp);
315 if (exp->tagq.tqh_first == NULL) {
316 msgq(sp, M_INFO, "The tags stack is empty.");
317 return (1);
318 }
319
320 switch (cmdp->argc) {
321 case 0: /* Pop one tag. */
322 tp = exp->tagq.tqh_first;
323 FREETAG(tp);
324 break;
325 case 1: /* Name or number. */
326 arg = cmdp->argv[0]->bp;
327 saved_off = strtol(arg, &p, 10);
328 if (*p == '\0') {
329 if (saved_off < 1)
330 return (0);
331 for (tp = exp->tagq.tqh_first, off = saved_off;
332 tp != NULL && off-- > 1; tp = tp->q.tqe_next);
333 if (tp == NULL) {
334 msgq(sp, M_ERR,
335"Less than %d entries on the tags stack; use :display to see the tags stack.",
336 saved_off);
337 return (1);
338 }
339 for (off = saved_off; off-- > 1;) {
340 tp = exp->tagq.tqh_first;
341 FREETAG(tp);
342 }
343 } else {
344 arglen = strlen(arg);
345 for (tp = exp->tagq.tqh_first;
346 tp != NULL; tp = tp->q.tqe_next) {
347 /* Use the user's original file name. */
348 p = tp->frp->name;
349 if ((t = strrchr(p, '/')) == NULL)
350 t = p;
351 else
352 ++t;
353 if (!strncmp(arg, t, arglen)) {
354 ntp = tp;
355 break;
356 }
357 }
358 if (tp == NULL) {
359 msgq(sp, M_ERR,
360"No file named %s on the tags stack; use :display to see the tags stack.",
361 arg);
362 return (1);
363 }
364 for (;;) {
365 tp = exp->tagq.tqh_first;
366 if (tp == ntp)
367 break;
368 FREETAG(tp);
369 }
370 }
371 break;
372 default:
373 abort();
374 }
375
376 /* Update the cursor from the saved TAG information. */
377 tp = exp->tagq.tqh_first;
378 if (tp->frp == sp->frp) {
379 sp->lno = tp->lno;
380 sp->cno = tp->cno;
381 } else {
382 MODIFY_CHECK(sp, ep, F_ISSET(cmdp, E_FORCE));
383
384 if (file_init(sp, tp->frp, NULL, 0))
385 return (1);
386
387 tp->frp->lno = tp->lno;
388 tp->frp->cno = tp->cno;
389 F_SET(sp->frp, FR_CURSORSET);
390
391 F_SET(sp, S_FSWITCH);
392 }
393
394 /* If returning to the first tag, the stack is now empty. */
395 if (tp->q.tqe_next == NULL)
396 FREETAG(tp);
397 return (0);
398}
399
400/*
401 * ex_tagtop -- :tagt[op][!]
402 * Clear the tag stack.
403 */
404int
405ex_tagtop(sp, ep, cmdp)
406 SCR *sp;
407 EXF *ep;
408 EXCMDARG *cmdp;
409{
410 EX_PRIVATE *exp;
411 TAG *tp, tmp;
412 int found;
413
414 /* Pop to oldest saved information. */
415 exp = EXP(sp);
416 for (found = 0; (tp = exp->tagq.tqh_first) != NULL; found = 1) {
417 if (exp->tagq.tqh_first == NULL)
418 tmp = *tp;
419 FREETAG(tp);
420 }
421
422 if (!found) {
423 msgq(sp, M_INFO, "The tags stack is empty.");
424 return (1);
425 }
426
427 /* If not switching files, it's easy; else do the work. */
428 if (tmp.frp == sp->frp) {
429 sp->lno = tmp.lno;
430 sp->cno = tmp.cno;
431 } else {
432 MODIFY_CHECK(sp, sp->ep, F_ISSET(cmdp, E_FORCE));
433
434 if (file_init(sp, tmp.frp, NULL, 0))
435 return (1);
436
437 tmp.frp->lno = tmp.lno;
438 tmp.frp->cno = tmp.cno;
439
440 F_SET(sp->frp, FR_CURSORSET);
441
442 F_SET(sp, S_FSWITCH);
443 }
444 return (0);
445}
446
447/*
448 * ex_tagdisplay --
449 * Display the list of tags.
450 */
451int
452ex_tagdisplay(sp, ep)
453 SCR *sp;
454 EXF *ep;
455{
456 EX_PRIVATE *exp;
457 TAG *tp;
458 size_t len, maxlen;
459 int cnt;
460 char *name;
461
462 exp = EXP(sp);
463 if ((tp = exp->tagq.tqh_first) == NULL) {
464 (void)ex_printf(EXCOOKIE, "No tags to display.\n");
465 return (0);
466 }
467
468 /*
469 * Figure out the formatting. MNOC is the maximum
470 * number of file name columns before we split the line.
471 */
472#define MNOC 15
473 for (maxlen = 0,
474 tp = exp->tagq.tqh_first; tp != NULL; tp = tp->q.tqe_next) {
475 len = strlen(name = tp->frp->name); /* The original name. */
476 if (maxlen < len && len < MNOC)
477 maxlen = len;
478 }
479
480 for (cnt = 1, tp = exp->tagq.tqh_first; tp != NULL;
481 ++cnt, tp = tp->q.tqe_next) {
482 len = strlen(name = tp->frp->name); /* The original name. */
483 if (len > maxlen || len + tp->slen > sp->cols)
484 if (tp == NULL || tp->search == NULL)
485 (void)ex_printf(EXCOOKIE,
486 "%2d %s\n", cnt, name);
487 else
488 (void)ex_printf(EXCOOKIE,
489 "%2d %s\n** %*.*s %s\n", cnt, name,
490 (int)maxlen, (int)maxlen, "", tp->search);
491 else
492 if (tp == NULL || tp->search == NULL)
493 (void)ex_printf(EXCOOKIE, "%2d %*.*s\n",
494 cnt, (int)maxlen, (int)len, name);
495 else
496 (void)ex_printf(EXCOOKIE, "%2d %*.*s %s\n",
497 cnt, (int)maxlen, (int)len, name,
498 tp->search);
499 }
500 return (0);
501}
502
503/*
504 * ex_tagalloc --
505 * Create a new list of tag files.
506 */
507int
508ex_tagalloc(sp, str)
509 SCR *sp;
510 char *str;
511{
512 EX_PRIVATE *exp;
513 TAGF *tp;
514 size_t len;
515 char *p, *t;
516
517 /* Free current queue. */
518 exp = EXP(sp);
519 while ((tp = exp->tagfq.tqh_first) != NULL)
520 FREETAGF(tp);
521
522 /* Create new queue. */
523 for (p = t = str;; ++p) {
524 if (*p == '\0' || isblank(*p)) {
525 if ((len = p - t) > 1) {
526 MALLOC_RET(sp, tp, TAGF *, sizeof(TAGF));
527 MALLOC(sp, tp->name, char *, len + 1);
528 if (tp->name == NULL) {
529 FREE(tp, sizeof(TAGF));
530 return (1);
531 }
532 memmove(tp->name, t, len);
533 tp->name[len] = '\0';
534 tp->flags = 0;
535 TAILQ_INSERT_TAIL(&exp->tagfq, tp, q);
536 }
537 t = p + 1;
538 }
539 if (*p == '\0')
540 break;
541 }
542 return (0);
543}
544 /* Free previous queue. */
545/*
546 * ex_tagfree --
547 * Free the tags file list.
548 */
549int
550ex_tagfree(sp)
551 SCR *sp;
552{
553 EX_PRIVATE *exp;
554 TAG *tp;
555 TAGF *tfp;
556
557 /* Free up tag information. */
558 exp = EXP(sp);
559 while ((tp = exp->tagq.tqh_first) != NULL)
560 FREETAG(tp);
561 while ((tfp = exp->tagfq.tqh_first) != NULL)
562 FREETAGF(tfp);
563 FREE(exp->tlast, strlen(exp->tlast) + 1);
564 return (0);
565}
566
567/*
568 * ex_tagcopy --
569 * Copy a screen's tag structures.
570 */
571int
572ex_tagcopy(orig, sp)
573 SCR *orig, *sp;
574{
575 EX_PRIVATE *oexp, *nexp;
576 TAG *ap, *tp;
577 TAGF *atfp, *tfp;
578
579 /* Copy tag stack. */
580 oexp = EXP(orig);
581 nexp = EXP(sp);
582 for (ap = oexp->tagq.tqh_first; ap != NULL; ap = ap->q.tqe_next) {
583 MALLOC(sp, tp, TAG *, sizeof(TAG));
584 if (tp == NULL)
585 goto nomem;
586 *tp = *ap;
587 if (ap->search != NULL &&
588 (tp->search = strdup(ap->search)) == NULL)
589 goto nomem;
590 TAILQ_INSERT_TAIL(&nexp->tagq, tp, q);
591 }
592
593 /* Copy list of tag files. */
594 for (atfp = oexp->tagfq.tqh_first;
595 atfp != NULL; atfp = atfp->q.tqe_next) {
596 MALLOC(sp, tfp, TAGF *, sizeof(TAGF));
597 if (tfp == NULL)
598 goto nomem;
599 *tfp = *atfp;
600 if ((tfp->name = strdup(atfp->name)) == NULL)
601 goto nomem;
602 TAILQ_INSERT_TAIL(&nexp->tagfq, tfp, q);
603 }
604
605 /* Copy the last tag. */
606 if (oexp->tlast != NULL &&
607 (nexp->tlast = strdup(oexp->tlast)) == NULL) {
608nomem: msgq(sp, M_SYSERR, NULL);
609 return (1);
610 }
611 return (0);
612}
613
614/*
615 * tag_get --
616 * Get a tag from the tags files.
617 */
618static int
619tag_get(sp, tag, tagp, filep, searchp)
620 SCR *sp;
621 char *tag, **tagp, **filep, **searchp;
622{
623 EX_PRIVATE *exp;
624 TAGF *tfp;
625 int dne;
626 char *p;
627
628 /*
629 * Find the tag, only display missing file messages once, and
630 * then only if we didn't find the tag.
631 */
632 dne = 0;
633 exp = EXP(sp);
634 for (p = NULL, tfp = exp->tagfq.tqh_first;
635 tfp != NULL && p == NULL; tfp = tfp->q.tqe_next) {
636 errno = 0;
637 F_CLR(tfp, TAGF_DNE);
638 if (search(sp, tfp->name, tag, &p))
639 if (errno == ENOENT) {
640 if (!F_ISSET(tfp, TAGF_DNE_WARN)) {
641 dne = 1;
642 F_SET(tfp, TAGF_DNE);
643 }
644 } else
645 msgq(sp, M_SYSERR, tfp->name);
646 }
647
648 if (p == NULL) {
649 msgq(sp, M_ERR, "%s: tag not found.", tag);
650 if (dne)
651 for (tfp = exp->tagfq.tqh_first;
652 tfp != NULL; tfp = tfp->q.tqe_next)
653 if (F_ISSET(tfp, TAGF_DNE)) {
654 errno = ENOENT;
655 msgq(sp, M_SYSERR, tfp->name);
656 F_SET(tfp, TAGF_DNE_WARN);
657 }
658 return (1);
659 }
660
661 /*
662 * Set the return pointers; tagp points to the tag, and, incidentally
663 * the allocated string, filep points to the nul-terminated file name,
664 * searchp points to the nul-terminated search string.
665 */
666 for (*tagp = p; *p && !isblank(*p); ++p);
667 if (*p == '\0')
668 goto malformed;
669 for (*p++ = '\0'; isblank(*p); ++p);
670 for (*filep = p; *p && !isblank(*p); ++p);
671 if (*p == '\0')
672 goto malformed;
673 for (*p++ = '\0'; isblank(*p); ++p);
674 *searchp = p;
675 if (*p == '\0') {
676malformed: free(*tagp);
677 msgq(sp, M_ERR, "%s: corrupted tag in %s.", tag, tfp->name);
678 return (1);
679 }
680 return (0);
681}
682
683#define EQUAL 0
684#define GREATER 1
685#define LESS (-1)
686
687/*
688 * search --
689 * Search a file for a tag.
690 */
691static int
692search(sp, name, tname, tag)
693 SCR *sp;
694 char *name, *tname, **tag;
695{
696 struct stat sb;
697 int fd, len;
698 char *endp, *back, *front, *map, *p;
699
700 if ((fd = open(name, O_RDONLY, 0)) < 0)
701 return (1);
702
703 /*
704 * XXX
705 * We'd like to test if the file is too big to mmap. Since we don't
706 * know what size or type off_t's or size_t's are, what the largest
707 * unsigned integral type is, or what random insanity the local C
708 * compiler will perpetrate, doing the comparison in a portable way
709 * is flatly impossible. Hope that malloc fails if the file is too
710 * large.
711 */
712 if (fstat(fd, &sb) || (map = mmap(NULL, (size_t)sb.st_size,
713 PROT_READ, MAP_PRIVATE, fd, (off_t)0)) == (caddr_t)-1) {
714 (void)close(fd);
715 return (1);
716 }
717 front = map;
718 back = front + sb.st_size;
719
720 front = binary_search(tname, front, back);
721 front = linear_search(tname, front, back);
722
723 if (front == NULL || (endp = strchr(front, '\n')) == NULL) {
724 *tag = NULL;
725 goto done;
726 }
727
728 len = endp - front;
729 MALLOC(sp, p, char *, len + 1);
730 if (p == NULL) {
731 *tag = NULL;
732 goto done;
733 }
734 memmove(p, front, len);
735 p[len] = '\0';
736 *tag = p;
737
738done: if (munmap(map, (size_t)sb.st_size))
739 msgq(sp, M_SYSERR, "munmap");
740 if (close(fd))
741 msgq(sp, M_SYSERR, "close");
742 return (0);
743}
744
745/*
746 * Binary search for "string" in memory between "front" and "back".
747 *
748 * This routine is expected to return a pointer to the start of a line at
749 * *or before* the first word matching "string". Relaxing the constraint
750 * this way simplifies the algorithm.
751 *
752 * Invariants:
753 * front points to the beginning of a line at or before the first
754 * matching string.
755 *
756 * back points to the beginning of a line at or after the first
757 * matching line.
758 *
759 * Base of the Invariants.
760 * front = NULL;
761 * back = EOF;
762 *
763 * Advancing the Invariants:
764 *
765 * p = first newline after halfway point from front to back.
766 *
767 * If the string at "p" is not greater than the string to match,
768 * p is the new front. Otherwise it is the new back.
769 *
770 * Termination:
771 *
772 * The definition of the routine allows it return at any point,
773 * since front is always at or before the line to print.
774 *
775 * In fact, it returns when the chosen "p" equals "back". This
776 * implies that there exists a string is least half as long as
777 * (back - front), which in turn implies that a linear search will
778 * be no more expensive than the cost of simply printing a string or two.
779 *
780 * Trying to continue with binary search at this point would be
781 * more trouble than it's worth.
782 */
783#define SKIP_PAST_NEWLINE(p, back) while (p < back && *p++ != '\n');
784
785static char *
786binary_search(string, front, back)
787 register char *string, *front, *back;
788{
789 register char *p;
790
791 p = front + (back - front) / 2;
792 SKIP_PAST_NEWLINE(p, back);
793
794 while (p != back) {
795 if (compare(string, p, back) == GREATER)
796 front = p;
797 else
798 back = p;
799 p = front + (back - front) / 2;
800 SKIP_PAST_NEWLINE(p, back);
801 }
802 return (front);
803}
804
805/*
806 * Find the first line that starts with string, linearly searching from front
807 * to back.
808 *
809 * Return NULL for no such line.
810 *
811 * This routine assumes:
812 *
813 * o front points at the first character in a line.
814 * o front is before or at the first line to be printed.
815 */
816static char *
817linear_search(string, front, back)
818 char *string, *front, *back;
819{
820 while (front < back) {
821 switch (compare(string, front, back)) {
822 case EQUAL: /* Found it. */
823 return (front);
824 break;
825 case LESS: /* No such string. */
826 return (NULL);
827 break;
828 case GREATER: /* Keep going. */
829 break;
830 }
831 SKIP_PAST_NEWLINE(front, back);
832 }
833 return (NULL);
834}
835
836/*
837 * Return LESS, GREATER, or EQUAL depending on how the string1 compares
838 * with string2 (s1 ??? s2).
839 *
840 * o Matches up to len(s1) are EQUAL.
841 * o Matches up to len(s2) are GREATER.
842 *
843 * The string "s1" is null terminated. The string s2 is '\t', space, (or
844 * "back") terminated.
845 *
846 * !!!
847 * Reasonably modern ctags programs use tabs as separators, not spaces.
848 * However, historic programs did use spaces, and, I got complaints.
849 */
850static int
851compare(s1, s2, back)
852 register char *s1, *s2, *back;
853{
854 for (; *s1 && s2 < back && (*s2 != '\t' && *s2 != ' '); ++s1, ++s2)
855 if (*s1 != *s2)
856 return (*s1 < *s2 ? LESS : GREATER);
857 return (*s1 ? GREATER : s2 < back &&
858 (*s2 != '\t' && *s2 != ' ') ? LESS : EQUAL);
859}