]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/nvi/ex/ex_tag.c
This commit was generated by cvs2svn to compensate for changes in r46275,
[FreeBSD/FreeBSD.git] / contrib / nvi / ex / ex_tag.c
1 /*-
2  * Copyright (c) 1992, 1993, 1994
3  *      The Regents of the University of California.  All rights reserved.
4  * Copyright (c) 1992, 1993, 1994, 1995, 1996
5  *      Keith Bostic.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * David Hitz of Auspex Systems, Inc.
9  *
10  * See the LICENSE file for redistribution information.
11  */
12
13 #include "config.h"
14
15 #ifndef lint
16 static const char sccsid[] = "@(#)ex_tag.c      10.36 (Berkeley) 9/15/96";
17 #endif /* not lint */
18
19 #include <sys/param.h>
20 #include <sys/types.h>          /* XXX: param.h may not have included types.h */
21
22 #ifdef HAVE_SYS_MMAN_H
23 #include <sys/mman.h>
24 #endif
25
26 #include <sys/queue.h>
27 #include <sys/stat.h>
28 #include <sys/time.h>
29
30 #include <bitstring.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <limits.h>
35 #include <stddef.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40
41 #include "../common/common.h"
42 #include "../vi/vi.h"
43 #include "tag.h"
44
45 static char     *binary_search __P((char *, char *, char *));
46 static int       compare __P((char *, char *, char *));
47 static void      ctag_file __P((SCR *, TAGF *, char *, char **, size_t *));
48 static int       ctag_search __P((SCR *, char *, size_t, char *));
49 #ifdef GTAGS
50 static int       getentry __P((char *, char *, char *, char *));
51 static TAGQ     *gtag_slist __P((SCR *, char *, int));
52 #endif
53 static int       ctag_sfile __P((SCR *, TAGF *, TAGQ *, char *));
54 static TAGQ     *ctag_slist __P((SCR *, char *));
55 static char     *linear_search __P((char *, char *, char *));
56 static int       tag_copy __P((SCR *, TAG *, TAG **));
57 static int       tag_pop __P((SCR *, TAGQ *, int));
58 static int       tagf_copy __P((SCR *, TAGF *, TAGF **));
59 static int       tagf_free __P((SCR *, TAGF *));
60 static int       tagq_copy __P((SCR *, TAGQ *, TAGQ **));
61
62 /*
63  * ex_tag_first --
64  *      The tag code can be entered from main, e.g., "vi -t tag".
65  *
66  * PUBLIC: int ex_tag_first __P((SCR *, char *));
67  */
68 int
69 ex_tag_first(sp, tagarg)
70         SCR *sp;
71         char *tagarg;
72 {
73         ARGS *ap[2], a;
74         EXCMD cmd;
75
76         /* Build an argument for the ex :tag command. */
77         ex_cinit(&cmd, C_TAG, 0, OOBLNO, OOBLNO, 0, ap);
78         ex_cadd(&cmd, &a, tagarg, strlen(tagarg));
79
80         /*
81          * XXX
82          * Historic vi went ahead and created a temporary file when it failed
83          * to find the tag.  We match historic practice, but don't distinguish
84          * between real error and failure to find the tag.
85          */
86         if (ex_tag_push(sp, &cmd))
87                 return (0);
88
89         /* Display tags in the center of the screen. */
90         F_CLR(sp, SC_SCR_TOP);
91         F_SET(sp, SC_SCR_CENTER);
92
93         return (0);
94 }
95
96 #ifdef GTAGS
97 /*
98  * ex_rtag_push -- ^]
99  *                :rtag[!] [string]
100  *
101  * Enter a new TAGQ context based on a ctag string.
102  *
103  * PUBLIC: int ex_rtag_push __P((SCR *, EXCMD *));
104  */
105 int
106 ex_rtag_push(sp, cmdp)
107         SCR *sp;
108         EXCMD *cmdp;
109 {
110         F_SET(cmdp, E_REFERENCE);
111         return ex_tag_push(sp, cmdp);
112 }
113 #endif
114
115 /*
116  * ex_tag_push -- ^]
117  *                :tag[!] [string]
118  *
119  * Enter a new TAGQ context based on a ctag string.
120  *
121  * PUBLIC: int ex_tag_push __P((SCR *, EXCMD *));
122  */
123 int
124 ex_tag_push(sp, cmdp)
125         SCR *sp;
126         EXCMD *cmdp;
127 {
128         EX_PRIVATE *exp;
129         FREF *frp;
130         TAG *rtp;
131         TAGQ *rtqp, *tqp;
132         recno_t lno;
133         size_t cno;
134         long tl;
135         int force, istmp;
136
137         exp = EXP(sp);
138         switch (cmdp->argc) {
139         case 1:
140                 if (exp->tag_last != NULL)
141                         free(exp->tag_last);
142
143                 if ((exp->tag_last = strdup(cmdp->argv[0]->bp)) == NULL) {
144                         msgq(sp, M_SYSERR, NULL);
145                         return (1);
146                 }
147
148                 /* Taglength may limit the number of characters. */
149                 if ((tl =
150                     O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(exp->tag_last) > tl)
151                         exp->tag_last[tl] = '\0';
152                 break;
153         case 0:
154                 if (exp->tag_last == NULL) {
155                         msgq(sp, M_ERR, "158|No previous tag entered");
156                         return (1);
157                 }
158                 break;
159         default:
160                 abort();
161         }
162
163         /* Get the tag information. */
164 #ifdef GTAGS
165         if (O_ISSET(sp, O_GTAGSMODE)) {
166                 if ((tqp = gtag_slist(sp, exp->tag_last, F_ISSET(cmdp, E_REFERENCE))) == NULL)
167                         return (1);
168         } else
169 #endif
170         if ((tqp = ctag_slist(sp, exp->tag_last)) == NULL)
171                 return (1);
172
173         /*
174          * Allocate all necessary memory before swapping screens.  Initialize
175          * flags so we know what to free.
176          */
177         rtp = NULL;
178         rtqp = NULL;
179         if (exp->tq.cqh_first == (void *)&exp->tq) {
180                 /* Initialize the `local context' tag queue structure. */
181                 CALLOC_GOTO(sp, rtqp, TAGQ *, 1, sizeof(TAGQ));
182                 CIRCLEQ_INIT(&rtqp->tagq);
183
184                 /* Initialize and link in its tag structure. */
185                 CALLOC_GOTO(sp, rtp, TAG *, 1, sizeof(TAG));
186                 CIRCLEQ_INSERT_HEAD(&rtqp->tagq, rtp, q);
187                 rtqp->current = rtp;
188         }
189
190         /*
191          * Stick the current context information in a convenient place, we're
192          * about to lose it.  Note, if we're called on editor startup, there
193          * will be no FREF structure.
194          */
195         frp = sp->frp;
196         lno = sp->lno;
197         cno = sp->cno;
198         istmp = frp == NULL ||
199             F_ISSET(frp, FR_TMPFILE) && !F_ISSET(cmdp, E_NEWSCREEN);
200
201         /* Try to switch to the tag. */
202         force = FL_ISSET(cmdp->iflags, E_C_FORCE);
203         if (F_ISSET(cmdp, E_NEWSCREEN)) {
204                 if (ex_tag_Nswitch(sp, tqp->tagq.cqh_first, force))
205                         goto err;
206
207                 /* Everything else gets done in the new screen. */
208                 sp = sp->nextdisp;
209                 exp = EXP(sp);
210         } else
211                 if (ex_tag_nswitch(sp, tqp->tagq.cqh_first, force))
212                         goto err;
213
214         /*
215          * If this is the first tag, put a `current location' queue entry
216          * in place, so we can pop all the way back to the current mark.
217          * Note, it doesn't point to much of anything, it's a placeholder.
218          */
219         if (exp->tq.cqh_first == (void *)&exp->tq) {
220                 CIRCLEQ_INSERT_HEAD(&exp->tq, rtqp, q);
221         } else
222                 rtqp = exp->tq.cqh_first;
223
224         /* Link the new TAGQ structure into place. */
225         CIRCLEQ_INSERT_HEAD(&exp->tq, tqp, q);
226
227         (void)ctag_search(sp,
228             tqp->current->search, tqp->current->slen, tqp->tag);
229
230         /*
231          * Move the current context from the temporary save area into the
232          * right structure.
233          *
234          * If we were in a temporary file, we don't have a context to which
235          * we can return, so just make it be the same as what we're moving
236          * to.  It will be a little odd that ^T doesn't change anything, but
237          * I don't think it's a big deal.
238          */
239         if (istmp) {
240                 rtqp->current->frp = sp->frp;
241                 rtqp->current->lno = sp->lno;
242                 rtqp->current->cno = sp->cno;
243         } else {
244                 rtqp->current->frp = frp;
245                 rtqp->current->lno = lno;
246                 rtqp->current->cno = cno;
247         }
248         return (0);
249
250 err:
251 alloc_err:
252         if (rtqp != NULL)
253                 free(rtqp);
254         if (rtp != NULL)
255                 free(rtp);
256         tagq_free(sp, tqp);
257         return (1);
258 }
259
260 /* 
261  * ex_tag_next --
262  *      Switch context to the next TAG.
263  *
264  * PUBLIC: int ex_tag_next __P((SCR *, EXCMD *));
265  */
266 int
267 ex_tag_next(sp, cmdp)
268         SCR *sp;
269         EXCMD *cmdp;
270 {
271         EX_PRIVATE *exp;
272         TAG *tp;
273         TAGQ *tqp;
274
275         exp = EXP(sp);
276         if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
277                 tag_msg(sp, TAG_EMPTY, NULL);
278                 return (1);
279         }
280         if ((tp = tqp->current->q.cqe_next) == (void *)&tqp->tagq) {
281                 msgq(sp, M_ERR, "282|Already at the last tag of this group");
282                 return (1);
283         }
284         if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
285                 return (1);
286         tqp->current = tp;
287
288         if (F_ISSET(tqp, TAG_CSCOPE))
289                 (void)cscope_search(sp, tqp, tp);
290         else
291                 (void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
292         return (0);
293 }
294
295 /* 
296  * ex_tag_prev --
297  *      Switch context to the next TAG.
298  *
299  * PUBLIC: int ex_tag_prev __P((SCR *, EXCMD *));
300  */
301 int
302 ex_tag_prev(sp, cmdp)
303         SCR *sp;
304         EXCMD *cmdp;
305 {
306         EX_PRIVATE *exp;
307         TAG *tp;
308         TAGQ *tqp;
309
310         exp = EXP(sp);
311         if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
312                 tag_msg(sp, TAG_EMPTY, NULL);
313                 return (0);
314         }
315         if ((tp = tqp->current->q.cqe_prev) == (void *)&tqp->tagq) {
316                 msgq(sp, M_ERR, "255|Already at the first tag of this group");
317                 return (1);
318         }
319         if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
320                 return (1);
321         tqp->current = tp;
322
323         if (F_ISSET(tqp, TAG_CSCOPE))
324                 (void)cscope_search(sp, tqp, tp);
325         else
326                 (void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
327         return (0);
328 }
329
330 /*
331  * ex_tag_nswitch --
332  *      Switch context to the specified TAG.
333  *
334  * PUBLIC: int ex_tag_nswitch __P((SCR *, TAG *, int));
335  */
336 int
337 ex_tag_nswitch(sp, tp, force)
338         SCR *sp;
339         TAG *tp;
340         int force;
341 {
342         /* Get a file structure. */
343         if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)
344                 return (1);
345
346         /* If not changing files, return, we're done. */
347         if (tp->frp == sp->frp)
348                 return (0);
349
350         /* Check for permission to leave. */
351         if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
352                 return (1);
353
354         /* Initialize the new file. */
355         if (file_init(sp, tp->frp, NULL, FS_SETALT))
356                 return (1);
357
358         /* Display tags in the center of the screen. */
359         F_CLR(sp, SC_SCR_TOP);
360         F_SET(sp, SC_SCR_CENTER);
361
362         /* Switch. */
363         F_SET(sp, SC_FSWITCH);
364         return (0);
365 }
366
367 /*
368  * ex_tag_Nswitch --
369  *      Switch context to the specified TAG in a new screen.
370  *
371  * PUBLIC: int ex_tag_Nswitch __P((SCR *, TAG *, int));
372  */
373 int
374 ex_tag_Nswitch(sp, tp, force)
375         SCR *sp;
376         TAG *tp;
377         int force;
378 {
379         SCR *new;
380
381         /* Get a file structure. */
382         if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)
383                 return (1);
384
385         /* Get a new screen. */
386         if (screen_init(sp->gp, sp, &new))
387                 return (1);
388         if (vs_split(sp, new, 0)) {
389                 (void)file_end(new, new->ep, 1);
390                 (void)screen_end(new);
391                 return (1);
392         }
393
394         /* Get a backing file. */
395         if (tp->frp == sp->frp) {
396                 /* Copy file state. */
397                 new->ep = sp->ep;
398                 ++new->ep->refcnt;
399
400                 new->frp = tp->frp;
401                 new->frp->flags = sp->frp->flags;
402         } else if (file_init(new, tp->frp, NULL, force)) {
403                 (void)vs_discard(new, NULL);
404                 (void)screen_end(new);
405                 return (1);
406         }
407
408         /* Create the argument list. */
409         new->cargv = new->argv = ex_buildargv(sp, NULL, tp->frp->name);
410
411         /* Display tags in the center of the screen. */
412         F_CLR(new, SC_SCR_TOP);
413         F_SET(new, SC_SCR_CENTER);
414
415         /* Switch. */
416         sp->nextdisp = new;
417         F_SET(sp, SC_SSWITCH);
418
419         return (0);
420 }
421
422 /*
423  * ex_tag_pop -- ^T
424  *               :tagp[op][!] [number | file]
425  *
426  *      Pop to a previous TAGQ context.
427  *
428  * PUBLIC: int ex_tag_pop __P((SCR *, EXCMD *));
429  */
430 int
431 ex_tag_pop(sp, cmdp)
432         SCR *sp;
433         EXCMD *cmdp;
434 {
435         EX_PRIVATE *exp;
436         TAGQ *tqp, *dtqp;
437         size_t arglen;
438         long off;
439         char *arg, *p, *t;
440
441         /* Check for an empty stack. */
442         exp = EXP(sp);
443         if (exp->tq.cqh_first == (void *)&exp->tq) {
444                 tag_msg(sp, TAG_EMPTY, NULL);
445                 return (1);
446         }
447
448         /* Find the last TAG structure that we're going to DISCARD! */
449         switch (cmdp->argc) {
450         case 0:                         /* Pop one tag. */
451                 dtqp = exp->tq.cqh_first;
452                 break;
453         case 1:                         /* Name or number. */
454                 arg = cmdp->argv[0]->bp;
455                 off = strtol(arg, &p, 10);
456                 if (*p != '\0')
457                         goto filearg;
458
459                 /* Number: pop that many queue entries. */
460                 if (off < 1)
461                         return (0);
462                 for (tqp = exp->tq.cqh_first;
463                     tqp != (void *)&exp->tq && --off > 1;
464                     tqp = tqp->q.cqe_next);
465                 if (tqp == (void *)&exp->tq) {
466                         msgq(sp, M_ERR,
467         "159|Less than %s entries on the tags stack; use :display t[ags]",
468                             arg);
469                         return (1);
470                 }
471                 dtqp = tqp;
472                 break;
473
474                 /* File argument: pop to that queue entry. */
475 filearg:        arglen = strlen(arg);
476                 for (tqp = exp->tq.cqh_first;
477                     tqp != (void *)&exp->tq;
478                     dtqp = tqp, tqp = tqp->q.cqe_next) {
479                         /* Don't pop to the current file. */
480                         if (tqp == exp->tq.cqh_first)
481                                 continue;
482                         p = tqp->current->frp->name;
483                         if ((t = strrchr(p, '/')) == NULL)
484                                 t = p;
485                         else
486                                 ++t;
487                         if (!strncmp(arg, t, arglen))
488                                 break;
489                 }
490                 if (tqp == (void *)&exp->tq) {
491                         msgq_str(sp, M_ERR, arg,
492         "160|No file %s on the tags stack to return to; use :display t[ags]");
493                         return (1);
494                 }
495                 if (tqp == exp->tq.cqh_first)
496                         return (0);
497                 break;
498         default:
499                 abort();
500         }
501
502         return (tag_pop(sp, dtqp, FL_ISSET(cmdp->iflags, E_C_FORCE)));
503 }
504
505 /*
506  * ex_tag_top -- :tagt[op][!]
507  *      Clear the tag stack.
508  *
509  * PUBLIC: int ex_tag_top __P((SCR *, EXCMD *));
510  */
511 int
512 ex_tag_top(sp, cmdp)
513         SCR *sp;
514         EXCMD *cmdp;
515 {
516         EX_PRIVATE *exp;
517
518         exp = EXP(sp);
519
520         /* Check for an empty stack. */
521         if (exp->tq.cqh_first == (void *)&exp->tq) {
522                 tag_msg(sp, TAG_EMPTY, NULL);
523                 return (1);
524         }
525
526         /* Return to the oldest information. */
527         return (tag_pop(sp,
528             exp->tq.cqh_last->q.cqe_prev, FL_ISSET(cmdp->iflags, E_C_FORCE)));
529 }
530
531 /*
532  * tag_pop --
533  *      Pop up to and including the specified TAGQ context.
534  */
535 static int
536 tag_pop(sp, dtqp, force)
537         SCR *sp;
538         TAGQ *dtqp;
539         int force;
540 {
541         EX_PRIVATE *exp;
542         TAG *tp;
543         TAGQ *tqp;
544
545         exp = EXP(sp);
546
547         /*
548          * Update the cursor from the saved TAG information of the TAG
549          * structure we're moving to.
550          */
551         tp = dtqp->q.cqe_next->current;
552         if (tp->frp == sp->frp) {
553                 sp->lno = tp->lno;
554                 sp->cno = tp->cno;
555         } else {
556                 if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
557                         return (1);
558
559                 tp->frp->lno = tp->lno;
560                 tp->frp->cno = tp->cno;
561                 F_SET(sp->frp, FR_CURSORSET);
562                 if (file_init(sp, tp->frp, NULL, FS_SETALT))
563                         return (1);
564
565                 F_SET(sp, SC_FSWITCH);
566         }
567
568         /* Pop entries off the queue up to and including dtqp. */
569         do {
570                 tqp = exp->tq.cqh_first;
571                 if (tagq_free(sp, tqp))
572                         return (0);
573         } while (tqp != dtqp);
574
575         /*
576          * If only a single tag left, we've returned to the first tag point,
577          * and the stack is now empty.
578          */
579         if (exp->tq.cqh_first->q.cqe_next == (void *)&exp->tq)
580                 tagq_free(sp, exp->tq.cqh_first);
581
582         return (0);
583 }
584
585 /*
586  * ex_tag_display --
587  *      Display the list of tags.
588  *
589  * PUBLIC: int ex_tag_display __P((SCR *));
590  */
591 int
592 ex_tag_display(sp)
593         SCR *sp;
594 {
595         EX_PRIVATE *exp;
596         TAG *tp;
597         TAGQ *tqp;
598         int cnt;
599         size_t len;
600         char *p, *sep;
601
602         exp = EXP(sp);
603         if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) {
604                 tag_msg(sp, TAG_EMPTY, NULL);
605                 return (0);
606         }
607
608         /*
609          * We give the file name 20 columns and the search string the rest.
610          * If there's not enough room, we don't do anything special, it's
611          * not worth the effort, it just makes the display more confusing.
612          *
613          * We also assume that characters in file names map 1-1 to printing
614          * characters.  This might not be true, but I don't think it's worth
615          * fixing.  (The obvious fix is to pass the filenames through the
616          * msg_print function.)
617          */
618 #define L_NAME  30              /* Name. */
619 #define L_SLOP   4              /* Leading number plus trailing *. */
620 #define L_SPACE  5              /* Spaces after name, before tag. */
621 #define L_TAG   20              /* Tag. */
622         if (sp->cols <= L_NAME + L_SLOP) {
623                 msgq(sp, M_ERR, "292|Display too small.");
624                 return (0);
625         }
626
627         /*
628          * Display the list of tags for each queue entry.  The first entry
629          * is numbered, and the current tag entry has an asterisk appended.
630          */
631         for (cnt = 1, tqp = exp->tq.cqh_first; !INTERRUPTED(sp) &&
632             tqp != (void *)&exp->tq; ++cnt, tqp = tqp->q.cqe_next)
633                 for (tp = tqp->tagq.cqh_first;
634                     tp != (void *)&tqp->tagq; tp = tp->q.cqe_next) {
635                         if (tp == tqp->tagq.cqh_first)
636                                 (void)ex_printf(sp, "%2d ", cnt);
637                         else
638                                 (void)ex_printf(sp, "   ");
639                         p = tp->frp == NULL ? tp->fname : tp->frp->name;
640                         if ((len = strlen(p)) > L_NAME) {
641                                 len = len - (L_NAME - 4);
642                                 (void)ex_printf(sp, "   ... %*.*s",
643                                     L_NAME - 4, L_NAME - 4, p + len);
644                         } else
645                                 (void)ex_printf(sp,
646                                     "   %*.*s", L_NAME, L_NAME, p);
647                         if (tqp->current == tp)
648                                 (void)ex_printf(sp, "*");
649
650                         if (tp == tqp->tagq.cqh_first && tqp->tag != NULL &&
651                             (sp->cols - L_NAME) >= L_TAG + L_SPACE) {
652                                 len = strlen(tqp->tag);
653                                 if (len > sp->cols - (L_NAME + L_SPACE))
654                                         len = sp->cols - (L_NAME + L_SPACE);
655                                 (void)ex_printf(sp, "%s%.*s",
656                                     tqp->current == tp ? "    " : "     ",
657                                     (int)len, tqp->tag);
658                         }
659                         (void)ex_printf(sp, "\n");
660                 }
661         return (0);
662 }
663
664 /*
665  * ex_tag_copy --
666  *      Copy a screen's tag structures.
667  *
668  * PUBLIC: int ex_tag_copy __P((SCR *, SCR *));
669  */
670 int
671 ex_tag_copy(orig, sp)
672         SCR *orig, *sp;
673 {
674         EX_PRIVATE *oexp, *nexp;
675         TAGQ *aqp, *tqp;
676         TAG *ap, *tp;
677         TAGF *atfp, *tfp;
678
679         oexp = EXP(orig);
680         nexp = EXP(sp);
681
682         /* Copy tag queue and tags stack. */
683         for (aqp = oexp->tq.cqh_first;
684             aqp != (void *)&oexp->tq; aqp = aqp->q.cqe_next) {
685                 if (tagq_copy(sp, aqp, &tqp))
686                         return (1);
687                 for (ap = aqp->tagq.cqh_first;
688                     ap != (void *)&aqp->tagq; ap = ap->q.cqe_next) {
689                         if (tag_copy(sp, ap, &tp))
690                                 return (1);
691                         /* Set the current pointer. */
692                         if (aqp->current == ap)
693                                 tqp->current = tp;
694                         CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q);
695                 }
696                 CIRCLEQ_INSERT_TAIL(&nexp->tq, tqp, q);
697         }
698
699         /* Copy list of tag files. */
700         for (atfp = oexp->tagfq.tqh_first;
701             atfp != NULL; atfp = atfp->q.tqe_next) {
702                 if (tagf_copy(sp, atfp, &tfp))
703                         return (1);
704                 TAILQ_INSERT_TAIL(&nexp->tagfq, tfp, q);
705         }
706
707         /* Copy the last tag. */
708         if (oexp->tag_last != NULL &&
709             (nexp->tag_last = strdup(oexp->tag_last)) == NULL) {
710                 msgq(sp, M_SYSERR, NULL);
711                 return (1);
712         }
713         return (0);
714 }
715
716 /*
717  * tagf_copy --
718  *      Copy a TAGF structure and return it in new memory.
719  */
720 static int
721 tagf_copy(sp, otfp, tfpp)
722         SCR *sp;
723         TAGF *otfp, **tfpp;
724 {
725         TAGF *tfp;
726
727         MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
728         *tfp = *otfp;
729
730         /* XXX: Allocate as part of the TAGF structure!!! */
731         if ((tfp->name = strdup(otfp->name)) == NULL)
732                 return (1);
733
734         *tfpp = tfp;
735         return (0);
736 }
737
738 /*
739  * tagq_copy --
740  *      Copy a TAGQ structure and return it in new memory.
741  */
742 static int
743 tagq_copy(sp, otqp, tqpp)
744         SCR *sp;
745         TAGQ *otqp, **tqpp;
746 {
747         TAGQ *tqp;
748         size_t len;
749
750         len = sizeof(TAGQ);
751         if (otqp->tag != NULL)
752                 len += otqp->tlen + 1;
753         MALLOC_RET(sp, tqp, TAGQ *, len);
754         memcpy(tqp, otqp, len);
755
756         CIRCLEQ_INIT(&tqp->tagq);
757         tqp->current = NULL;
758         if (otqp->tag != NULL)
759                 tqp->tag = tqp->buf;
760
761         *tqpp = tqp;
762         return (0);
763 }
764
765 /*
766  * tag_copy --
767  *      Copy a TAG structure and return it in new memory.
768  */
769 static int
770 tag_copy(sp, otp, tpp)
771         SCR *sp;
772         TAG *otp, **tpp;
773 {
774         TAG *tp;
775         size_t len;
776
777         len = sizeof(TAG);
778         if (otp->fname != NULL)
779                 len += otp->fnlen + 1;
780         if (otp->search != NULL)
781                 len += otp->slen + 1;
782         MALLOC_RET(sp, tp, TAG *, len);
783         memcpy(tp, otp, len);
784
785         if (otp->fname != NULL)
786                 tp->fname = tp->buf;
787         if (otp->search != NULL)
788                 tp->search = tp->fname + otp->fnlen + 1;
789
790         *tpp = tp;
791         return (0);
792 }
793
794 /*
795  * tagf_free --
796  *      Free a TAGF structure.
797  */
798 static int
799 tagf_free(sp, tfp)
800         SCR *sp;
801         TAGF *tfp;
802 {
803         EX_PRIVATE *exp;
804
805         exp = EXP(sp);
806         TAILQ_REMOVE(&exp->tagfq, tfp, q);
807         free(tfp->name);
808         free(tfp);
809         return (0);
810 }
811
812 /*
813  * tagq_free --
814  *      Free a TAGQ structure (and associated TAG structures).
815  *
816  * PUBLIC: int tagq_free __P((SCR *, TAGQ *));
817  */
818 int
819 tagq_free(sp, tqp)
820         SCR *sp;
821         TAGQ *tqp;
822 {
823         EX_PRIVATE *exp;
824         TAG *tp;
825
826         exp = EXP(sp);
827         while ((tp = tqp->tagq.cqh_first) != (void *)&tqp->tagq) {
828                 CIRCLEQ_REMOVE(&tqp->tagq, tp, q);
829                 free(tp);
830         }
831         /*
832          * !!!
833          * If allocated and then the user failed to switch files, the TAGQ
834          * structure was never attached to any list.
835          */
836         if (tqp->q.cqe_next != NULL)
837                 CIRCLEQ_REMOVE(&exp->tq, tqp, q);
838         free(tqp);
839         return (0);
840 }
841
842 /*
843  * tag_msg
844  *      A few common messages.
845  *
846  * PUBLIC: void tag_msg __P((SCR *, tagmsg_t, char *));
847  */
848 void
849 tag_msg(sp, msg, tag)
850         SCR *sp;
851         tagmsg_t msg;
852         char *tag;
853 {
854         switch (msg) {
855         case TAG_BADLNO:
856                 msgq_str(sp, M_ERR, tag,
857             "164|%s: the tag's line number is past the end of the file");
858                 break;
859         case TAG_EMPTY:
860                 msgq(sp, M_INFO, "165|The tags stack is empty");
861                 break;
862         case TAG_SEARCH:
863                 msgq_str(sp, M_ERR, tag, "166|%s: search pattern not found");
864                 break;
865         default:
866                 abort();
867         }
868 }
869
870 /*
871  * ex_tagf_alloc --
872  *      Create a new list of ctag files.
873  *
874  * PUBLIC: int ex_tagf_alloc __P((SCR *, char *));
875  */
876 int
877 ex_tagf_alloc(sp, str)
878         SCR *sp;
879         char *str;
880 {
881         EX_PRIVATE *exp;
882         TAGF *tfp;
883         size_t len;
884         char *p, *t;
885
886         /* Free current queue. */
887         exp = EXP(sp);
888         while ((tfp = exp->tagfq.tqh_first) != NULL)
889                 tagf_free(sp, tfp);
890
891         /* Create new queue. */
892         for (p = t = str;; ++p) {
893                 if (*p == '\0' || isblank(*p)) {
894                         if ((len = p - t) > 1) {
895                                 MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
896                                 MALLOC(sp, tfp->name, char *, len + 1);
897                                 if (tfp->name == NULL) {
898                                         free(tfp);
899                                         return (1);
900                                 }
901                                 memcpy(tfp->name, t, len);
902                                 tfp->name[len] = '\0';
903                                 tfp->flags = 0;
904                                 TAILQ_INSERT_TAIL(&exp->tagfq, tfp, q);
905                         }
906                         t = p + 1;
907                 }
908                 if (*p == '\0')
909                          break;
910         }
911         return (0);
912 }
913                                                 /* Free previous queue. */
914 /*
915  * ex_tag_free --
916  *      Free the ex tag information.
917  *
918  * PUBLIC: int ex_tag_free __P((SCR *));
919  */
920 int
921 ex_tag_free(sp)
922         SCR *sp;
923 {
924         EX_PRIVATE *exp;
925         TAGF *tfp;
926         TAGQ *tqp;
927
928         /* Free up tag information. */
929         exp = EXP(sp);
930         while ((tqp = exp->tq.cqh_first) != (void *)&exp->tq)
931                 tagq_free(sp, tqp);
932         while ((tfp = exp->tagfq.tqh_first) != NULL)
933                 tagf_free(sp, tfp);
934         if (exp->tag_last != NULL)
935                 free(exp->tag_last);
936         return (0);
937 }
938
939 /*
940  * ctag_search --
941  *      Search a file for a tag.
942  */
943 static int
944 ctag_search(sp, search, slen, tag)
945         SCR *sp;
946         char *search, *tag;
947         size_t slen;
948 {
949         MARK m;
950         char *p;
951
952         /*
953          * !!!
954          * The historic tags file format (from a long, long time ago...)
955          * used a line number, not a search string.  I got complaints, so
956          * people are still using the format.  POSIX 1003.2 permits it.
957          */
958         if (isdigit(search[0])) {
959                 m.lno = atoi(search);
960                 if (!db_exist(sp, m.lno)) {
961                         tag_msg(sp, TAG_BADLNO, tag);
962                         return (1);
963                 }
964         } else {
965                 /*
966                  * Search for the tag; cheap fallback for C functions
967                  * if the name is the same but the arguments have changed.
968                  */
969                 m.lno = 1;
970                 m.cno = 0;
971                 if (f_search(sp, &m, &m,
972                     search, slen, NULL, SEARCH_FILE | SEARCH_TAG))
973                         if ((p = strrchr(search, '(')) != NULL) {
974                                 slen = p - search;
975                                 if (f_search(sp, &m, &m, search, slen,
976                                     NULL, SEARCH_FILE | SEARCH_TAG))
977                                         goto notfound;
978                         } else {
979 notfound:                       tag_msg(sp, TAG_SEARCH, tag);
980                                 return (1);
981                         }
982                 /*
983                  * !!!
984                  * Historically, tags set the search direction if it wasn't
985                  * already set.
986                  */
987                 if (sp->searchdir == NOTSET)
988                         sp->searchdir = FORWARD;
989         }
990
991         /*
992          * !!!
993          * Tags move to the first non-blank, NOT the search pattern start.
994          */
995         sp->lno = m.lno;
996         sp->cno = 0;
997         (void)nonblank(sp, sp->lno, &sp->cno);
998         return (0);
999 }
1000
1001 #ifdef GTAGS
1002 /*
1003  * getentry --
1004  *      get tag information from current line.
1005  *
1006  * gtags temporary file format.
1007  * <tag>   <lineno>  <file>         <image>
1008  *
1009  * sample.
1010  * +------------------------------------------------
1011  * |main     30      main.c         main(argc, argv)
1012  * |func     21      subr.c         func(arg)
1013  */
1014 static int
1015 getentry(buf, tag, file, line)
1016         char *buf, *tag, *file, *line;
1017 {
1018         char *p;
1019
1020         p = tag;
1021         while (*buf && !isspace(*buf))          /* tag name */
1022                 *p++ = *buf++;
1023         *p = 0;
1024         while (*buf && isspace(*buf))           /* skip blanks */
1025                 buf++;
1026         p = line;
1027         while (*buf && !isspace(*buf))          /* line no */
1028                 *p++ = *buf++;
1029         *p = 0;
1030         while (*buf && isspace(*buf))           /* skip blanks */
1031                 buf++;
1032         p = file;
1033         while (*buf && !isspace(*buf))          /* file name */
1034                 *p++ = *buf++;
1035         *p = 0;
1036
1037         /* value check */
1038         if (strlen(tag) && strlen(line) && strlen(file) && atoi(line) > 0)
1039                 return 1;       /* OK */
1040         return 0;               /* ERROR */
1041 }
1042
1043 /*
1044  * gtag_slist --
1045  *      Search the list of tags files for a tag, and return tag queue.
1046  */
1047 static TAGQ *
1048 gtag_slist(sp, tag, ref)
1049         SCR *sp;
1050         char *tag;
1051         int ref;
1052 {
1053         EX_PRIVATE *exp;
1054         TAGF *tfp;
1055         TAGQ *tqp;
1056         size_t len;
1057         int echk;
1058         TAG *tp;
1059         static char name[80], file[200], line[10];
1060         char command[200];
1061         char buf[BUFSIZ+1];
1062         FILE *fp;
1063
1064         /* Allocate and initialize the tag queue structure. */
1065         len = strlen(tag);
1066         CALLOC_GOTO(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + len + 1);
1067         CIRCLEQ_INIT(&tqp->tagq);
1068         tqp->tag = tqp->buf;
1069         memcpy(tqp->tag, tag, (tqp->tlen = len) + 1);
1070
1071         /*
1072          * Find the tag, only display missing file messages once, and
1073          * then only if we didn't find the tag.
1074          */
1075         sprintf(command, "global -%s '%s'", ref ? "rx" : "x", tag);
1076         if (fp = popen(command, "r")) {
1077                 while (fgets(buf, sizeof(buf), fp)) {
1078                         if (buf[strlen(buf)-1] == '\n')         /* chop(buf) */
1079                                 buf[strlen(buf)-1] = 0;
1080                         else
1081                                 while (fgetc(fp) != '\n')
1082                                         ;
1083                         if (getentry(buf, name, file, line) == 0) {
1084                                 echk = 1;
1085                                 F_SET(tfp, TAGF_ERR);
1086                                 break;
1087                         }
1088                         CALLOC_GOTO(sp, tp,
1089                             TAG *, 1, sizeof(TAG) + strlen(file) + 1 + strlen(line) + 1);
1090                         tp->fname = tp->buf;
1091                         strcpy(tp->fname, file);
1092                         tp->fnlen = strlen(file);
1093                         tp->search = tp->fname + tp->fnlen + 1;
1094                         strcpy(tp->search, line);
1095                         CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q);
1096                 }
1097                 pclose(fp);
1098         }
1099
1100         /* Check to see if we found anything. */
1101         if (tqp->tagq.cqh_first == (void *)&tqp->tagq) {
1102                 msgq_str(sp, M_ERR, tag, "162|%s: tag not found");
1103                 free(tqp);
1104                 return (NULL);
1105         }
1106
1107         tqp->current = tqp->tagq.cqh_first;
1108         return (tqp);
1109
1110 alloc_err:
1111         return (NULL);
1112 }
1113 #endif
1114 /*
1115  * ctag_slist --
1116  *      Search the list of tags files for a tag, and return tag queue.
1117  */
1118 static TAGQ *
1119 ctag_slist(sp, tag)
1120         SCR *sp;
1121         char *tag;
1122 {
1123         EX_PRIVATE *exp;
1124         TAGF *tfp;
1125         TAGQ *tqp;
1126         size_t len;
1127         int echk;
1128
1129         exp = EXP(sp);
1130
1131         /* Allocate and initialize the tag queue structure. */
1132         len = strlen(tag);
1133         CALLOC_GOTO(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + len + 1);
1134         CIRCLEQ_INIT(&tqp->tagq);
1135         tqp->tag = tqp->buf;
1136         memcpy(tqp->tag, tag, (tqp->tlen = len) + 1);
1137
1138         /*
1139          * Find the tag, only display missing file messages once, and
1140          * then only if we didn't find the tag.
1141          */
1142         for (echk = 0,
1143             tfp = exp->tagfq.tqh_first; tfp != NULL; tfp = tfp->q.tqe_next)
1144                 if (ctag_sfile(sp, tfp, tqp, tag)) {
1145                         echk = 1;
1146                         F_SET(tfp, TAGF_ERR);
1147                 } else
1148                         F_CLR(tfp, TAGF_ERR | TAGF_ERR_WARN);
1149
1150         /* Check to see if we found anything. */
1151         if (tqp->tagq.cqh_first == (void *)&tqp->tagq) {
1152                 msgq_str(sp, M_ERR, tag, "162|%s: tag not found");
1153                 if (echk)
1154                         for (tfp = exp->tagfq.tqh_first;
1155                             tfp != NULL; tfp = tfp->q.tqe_next)
1156                                 if (F_ISSET(tfp, TAGF_ERR) &&
1157                                     !F_ISSET(tfp, TAGF_ERR_WARN)) {
1158                                         errno = tfp->errnum;
1159                                         msgq_str(sp, M_SYSERR, tfp->name, "%s");
1160                                         F_SET(tfp, TAGF_ERR_WARN);
1161                                 }
1162                 free(tqp);
1163                 return (NULL);
1164         }
1165
1166         tqp->current = tqp->tagq.cqh_first;
1167         return (tqp);
1168
1169 alloc_err:
1170         return (NULL);
1171 }
1172
1173 /*
1174  * ctag_sfile --
1175  *      Search a tags file for a tag, adding any found to the tag queue.
1176  */
1177 static int
1178 ctag_sfile(sp, tfp, tqp, tname)
1179         SCR *sp;
1180         TAGF *tfp;
1181         TAGQ *tqp;
1182         char *tname;
1183 {
1184         struct stat sb;
1185         TAG *tp;
1186         size_t dlen, nlen, slen;
1187         int fd, i, nf1, nf2;
1188         char *back, *cname, *dname, *front, *map, *name, *p, *search, *t;
1189
1190         if ((fd = open(tfp->name, O_RDONLY, 0)) < 0) {
1191                 tfp->errnum = errno;
1192                 return (1);
1193         }
1194
1195         /*
1196          * XXX
1197          * Some old BSD systems require MAP_FILE as an argument when mapping
1198          * regular files.
1199          */
1200 #ifndef MAP_FILE
1201 #define MAP_FILE        0
1202 #endif
1203         /*
1204          * XXX
1205          * We'd like to test if the file is too big to mmap.  Since we don't
1206          * know what size or type off_t's or size_t's are, what the largest
1207          * unsigned integral type is, or what random insanity the local C
1208          * compiler will perpetrate, doing the comparison in a portable way
1209          * is flatly impossible.  Hope mmap fails if the file is too large.
1210          */
1211         if (fstat(fd, &sb) != 0 ||
1212             (map = mmap(NULL, (size_t)sb.st_size, PROT_READ | PROT_WRITE,
1213             MAP_FILE | MAP_PRIVATE, fd, (off_t)0)) == (caddr_t)-1) {
1214                 tfp->errnum = errno;
1215                 (void)close(fd);
1216                 return (1);
1217         }
1218
1219         front = map;
1220         back = front + sb.st_size;
1221         front = binary_search(tname, front, back);
1222         front = linear_search(tname, front, back);
1223         if (front == NULL)
1224                 goto done;
1225
1226         /*
1227          * Initialize and link in the tag structure(s).  The historic ctags
1228          * file format only permitted a single tag location per tag.  The
1229          * obvious extension to permit multiple tags locations per tag is to
1230          * output multiple records in the standard format.  Unfortunately,
1231          * this won't work correctly with historic ex/vi implementations,
1232          * because their binary search assumes that there's only one record
1233          * per tag, and so will use a random tag entry if there si more than
1234          * one.  This code handles either format.
1235          *
1236          * The tags file is in the following format:
1237          *
1238          *      <tag> <filename> <line number> | <pattern>
1239          *
1240          * Figure out how long everything is so we can allocate in one swell
1241          * foop, but discard anything that looks wrong.
1242          */
1243         for (;;) {
1244                 /* Nul-terminate the end of the line. */
1245                 for (p = front; p < back && *p != '\n'; ++p);
1246                 if (p == back || *p != '\n')
1247                         break;
1248                 *p = '\0';
1249
1250                 /* Update the pointers for the next time. */
1251                 t = p + 1;
1252                 p = front;
1253                 front = t;
1254
1255                 /* Break the line into tokens. */
1256                 for (i = 0; i < 2 && (t = strsep(&p, "\t ")) != NULL; ++i)
1257                         switch (i) {
1258                         case 0:                 /* Tag. */
1259                                 cname = t;
1260                                 break;
1261                         case 1:                 /* Filename. */
1262                                 name = t;
1263                                 nlen = strlen(name);
1264                                 break;
1265                         }
1266
1267                 /* Check for corruption. */
1268                 if (i != 2 || p == NULL || t == NULL)
1269                         goto corrupt;
1270
1271                 /* The rest of the string is the search pattern. */
1272                 search = p;
1273                 if ((slen = strlen(p)) == 0) {
1274 corrupt:                p = msg_print(sp, tname, &nf1);
1275                         t = msg_print(sp, tfp->name, &nf2);
1276                         msgq(sp, M_ERR, "163|%s: corrupted tag in %s", p, t);
1277                         if (nf1)
1278                                 FREE_SPACE(sp, p, 0);
1279                         if (nf2)
1280                                 FREE_SPACE(sp, t, 0);
1281                         continue;
1282                 }
1283
1284                 /* Check for passing the last entry. */
1285                 if (strcmp(tname, cname))
1286                         break;
1287
1288                 /* Resolve the file name. */
1289                 ctag_file(sp, tfp, name, &dname, &dlen);
1290
1291                 CALLOC_GOTO(sp, tp,
1292                     TAG *, 1, sizeof(TAG) + dlen + 2 + nlen + 1 + slen + 1);
1293                 tp->fname = tp->buf;
1294                 if (dlen != 0) {
1295                         memcpy(tp->fname, dname, dlen);
1296                         tp->fname[dlen] = '/';
1297                         ++dlen;
1298                 }
1299                 memcpy(tp->fname + dlen, name, nlen + 1);
1300                 tp->fnlen = dlen + nlen;
1301                 tp->search = tp->fname + tp->fnlen + 1;
1302                 memcpy(tp->search, search, (tp->slen = slen) + 1);
1303                 CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q);
1304         }
1305
1306 alloc_err:
1307 done:   if (munmap(map, (size_t)sb.st_size))
1308                 msgq(sp, M_SYSERR, "munmap");
1309         if (close(fd))
1310                 msgq(sp, M_SYSERR, "close");
1311         return (0);
1312 }
1313
1314 /*
1315  * ctag_file --
1316  *      Search for the right path to this file.
1317  */
1318 static void
1319 ctag_file(sp, tfp, name, dirp, dlenp)
1320         SCR *sp;
1321         TAGF *tfp;
1322         char *name, **dirp;
1323         size_t *dlenp;
1324 {
1325         struct stat sb;
1326         size_t len;
1327         char *p, buf[MAXPATHLEN];
1328
1329         /*
1330          * !!!
1331          * If the tag file path is a relative path, see if it exists.  If it
1332          * doesn't, look relative to the tags file path.  It's okay for a tag
1333          * file to not exist, and historically, vi simply displayed a "new"
1334          * file.  However, if the path exists relative to the tag file, it's
1335          * pretty clear what's happening, so we may as well get it right.
1336          */
1337         *dlenp = 0;
1338         if (name[0] != '/' &&
1339             stat(name, &sb) && (p = strrchr(tfp->name, '/')) != NULL) {
1340                 *p = '\0';
1341                 len = snprintf(buf, sizeof(buf), "%s/%s", tfp->name, name);
1342                 *p = '/';
1343                 if (stat(buf, &sb) == 0) {
1344                         *dirp = tfp->name;
1345                         *dlenp = strlen(*dirp);
1346                 }
1347         }
1348 }
1349
1350 /*
1351  * Binary search for "string" in memory between "front" and "back".
1352  *
1353  * This routine is expected to return a pointer to the start of a line at
1354  * *or before* the first word matching "string".  Relaxing the constraint
1355  * this way simplifies the algorithm.
1356  *
1357  * Invariants:
1358  *      front points to the beginning of a line at or before the first
1359  *      matching string.
1360  *
1361  *      back points to the beginning of a line at or after the first
1362  *      matching line.
1363  *
1364  * Base of the Invariants.
1365  *      front = NULL;
1366  *      back = EOF;
1367  *
1368  * Advancing the Invariants:
1369  *
1370  *      p = first newline after halfway point from front to back.
1371  *
1372  *      If the string at "p" is not greater than the string to match,
1373  *      p is the new front.  Otherwise it is the new back.
1374  *
1375  * Termination:
1376  *
1377  *      The definition of the routine allows it return at any point,
1378  *      since front is always at or before the line to print.
1379  *
1380  *      In fact, it returns when the chosen "p" equals "back".  This
1381  *      implies that there exists a string is least half as long as
1382  *      (back - front), which in turn implies that a linear search will
1383  *      be no more expensive than the cost of simply printing a string or two.
1384  *
1385  *      Trying to continue with binary search at this point would be
1386  *      more trouble than it's worth.
1387  */
1388 #define EQUAL           0
1389 #define GREATER         1
1390 #define LESS            (-1)
1391
1392 #define SKIP_PAST_NEWLINE(p, back)      while (p < back && *p++ != '\n');
1393
1394 static char *
1395 binary_search(string, front, back)
1396         register char *string, *front, *back;
1397 {
1398         register char *p;
1399
1400         p = front + (back - front) / 2;
1401         SKIP_PAST_NEWLINE(p, back);
1402
1403         while (p != back) {
1404                 if (compare(string, p, back) == GREATER)
1405                         front = p;
1406                 else
1407                         back = p;
1408                 p = front + (back - front) / 2;
1409                 SKIP_PAST_NEWLINE(p, back);
1410         }
1411         return (front);
1412 }
1413
1414 /*
1415  * Find the first line that starts with string, linearly searching from front
1416  * to back.
1417  *
1418  * Return NULL for no such line.
1419  *
1420  * This routine assumes:
1421  *
1422  *      o front points at the first character in a line.
1423  *      o front is before or at the first line to be printed.
1424  */
1425 static char *
1426 linear_search(string, front, back)
1427         char *string, *front, *back;
1428 {
1429         while (front < back) {
1430                 switch (compare(string, front, back)) {
1431                 case EQUAL:             /* Found it. */
1432                         return (front);
1433                 case LESS:              /* No such string. */
1434                         return (NULL);
1435                 case GREATER:           /* Keep going. */
1436                         break;
1437                 }
1438                 SKIP_PAST_NEWLINE(front, back);
1439         }
1440         return (NULL);
1441 }
1442
1443 /*
1444  * Return LESS, GREATER, or EQUAL depending on how the string1 compares
1445  * with string2 (s1 ??? s2).
1446  *
1447  *      o Matches up to len(s1) are EQUAL.
1448  *      o Matches up to len(s2) are GREATER.
1449  *
1450  * The string "s1" is null terminated.  The string s2 is '\t', space, (or
1451  * "back") terminated.
1452  *
1453  * !!!
1454  * Reasonably modern ctags programs use tabs as separators, not spaces.
1455  * However, historic programs did use spaces, and, I got complaints.
1456  */
1457 static int
1458 compare(s1, s2, back)
1459         register char *s1, *s2, *back;
1460 {
1461         for (; *s1 && s2 < back && (*s2 != '\t' && *s2 != ' '); ++s1, ++s2)
1462                 if (*s1 != *s2)
1463                         return (*s1 < *s2 ? LESS : GREATER);
1464         return (*s1 ? GREATER : s2 < back &&
1465             (*s2 != '\t' && *s2 != ' ') ? LESS : EQUAL);
1466 }