/* Date: Thu 10 Jul 86 03:07:25-EDT From: Joseph M. Newcomer Subject: TAGS.C Added to Library I now have a working TAGS system for C and Epsilon. For those of you who don't know what TAGS is, it allows you to find the file which contains a definition if you know the name of the object being defined. For example, you are looking at a call on procedure xYzzY, and want to see what it does. Fine. Invoke TAGS by typing Alt-., give it the name xYzzY, and suddenly before your very orbs is the definition point for the function! If the cursor is sitting somewhere before a word, that word is the default name to look up and so A-. CR usually suffices. This requires running a program over your source file that locates all the tags and puts them in a tag dictionary, then having an editor command that looks things up in the tag dictionary. I am submitting the source for both of these. TAGS.C is the name of the file, and it contains its makefile and the editor support for Epsilon, TAGS.E and TAGS1.E. Tags is non-resident and loaded by a library load operation; included is the procedure to do the library load, which you can install in your Epsilon environment somewhere (I put mine in FILES.E). There are some caveats. I didn't write this, it was written by Richard M. Stallman of EMACS and GNU fame. It comes with his copyright, which I believe I have honored in providing full support source with it (namely Epsilon code). Please continue to honor this copyright. I added Scribe and TeX modes, but have only tested them minimally. I have not tested the Fortran or Lisp modes at all. .C and .E files seem to work. Feel free to enhance it. It seems to have various obscure switch options which I don't understand; I've inserted documentation showing the result of the various switches. I've also added a mode that allows it to interact reasonably with makefiles; this is documented in the comments. I have found it immensley useful after I went back to my 84-file 700,000 byte source after 6 months' absence; I can now find things. I offer it, unsupported and 'as is'. If it is broken, please feel free to make improvments and resubmit it. joe ------- Received: from PREP.AI.MIT.EDU by A.SEI.CMU.EDU; 29 May 86 19:02:21 EDT Received: by PREP.AI.MIT.EDU; Thu, 29 May 86 19:02:12 EDT Date: Thu, 29 May 86 19:02:12 EDT From: rms@PREP.AI.MIT.EDU (Richard M. Stallman) Message-Id: <8605292302.AA01542@prep.ai.mit.edu> To: Joe.Newcomer In-Reply-To: Joe.Newcomer's message of Wednesday, 28 May 1986 18:42:06 EDT Subject: tags */ /* Tags file maker to go with GNUmacs Copyright (C) 1984 Richard M. Stallman and Ken Arnold This program is distributed in the hope that it will be useful, but without any warranty. No author or distributor accepts responsibility to anyone for the consequences of using it or for whether it serves any particular purpose or works at all, unless he says so in writing. Permission is granted to anyone to distribute verbatim copies of this program's source code as received, in any medium, provided that the copyright notice, the nonwarraty notice above and this permission notice are preserved, and that the distributor grants the recipient all rights for further redistribution as permitted by this notice, and informs him of these rights. Permission is granted to distribute modified versions of this program's source code, or of portions of it, under the above conditions, plus the conditions that all changed files carry prominent notices stating who last changed them and that the derived material, including anything packaged together with it and conceptually functioning as a modification of it rather than an application of it, is in its entirety subject to a permission notice identical to this one. Permission is granted to distribute this program (verbatim or as modified) in compiled or executable form, provided verbatim redistribution is permitted as stated above for source code, and A. it is accompanied by the corresponding machine-readable source code, under the above conditions, or B. it is accompanied by a written offer, with no time limit, to distribute the corresponding machine-readable source code, under the above conditions, to any one, in return for reimbursement of the cost of distribution. Verbatim redistribution of the written offer must be permitted. Or, C. it is distributed by someone who received only the compiled or executable form, and is accompanied by a copy of the written offer of source code which he received along with it. Permission is granted to distribute this program (verbatim or as modified) in executable form as part of a larger system provided that the source code for this program, including any modifications used, is also distributed or offered as stated in the preceding paragraph. In other words, you are welcome to use, share and improve this program. You are forbidden to forbid anyone else to use, share and improve what you give them. Help stamp out software-hoarding! */ /* Converted to Lattice C 3.0 for MS/PC DOS by Joseph M. Newcomer June, 1986. Copyright 1986, Joseph M. Newcomer. Subject to the same copyright and distribution agreement as stated above. Non-hoardable software. Change notes: Several changes were required to convert this to Lattice C and MS/PC DOS. In particular, the 'system' command and case sensitivity in files do not exist and had to be replaced. Filename expansion is not implicitly done on the DOS command line and had to be explicitly installed. */ /***************************************************************************** User documentation invoke as tags [options] filename filename ... where each filename may be a DOS wildcard pattern filename. The options are: -e Produces mapping of name --> line,char -v Produces mapping of name --> file ((line+63) / 64) -x Produces human-readable xref name -> line file declaration none of the above Produces format suitable for Epsilon 'tags' mode -t include typedef symbols; -a appends current input result to 'tags' file -u updates existing 'tags' file (removes previous entry and appends new result on end) -B sets search delimiter to '?' -F sets search delimiter to '/' (default) -w suppresses warnings -m Writes .tag file (empty file to be used by makefile to regenerate tags that have changed) Normal use is one of the following forms tags file file file ... Creates 'tags' from the files listed, suitable for Epsilon tags-mode tags -u file1 file2 ... Replaces the entries for file1, file2, etc. in 'tags' (or adds them if they are not present) tags -m -u file1 Replaces the entries for file1 in 'tags' and updates the .tag file. This line would typically appear in the makefile as .tag.c : tags -m -u $< Included at the end of this listing is the makefile for tags itself. *****************************************************************************/ /* Documentation of output file format: -e flag ^L file_name,number_of_entries name\0177line,char name\0177line,char ... ----------------------------------------------------------------------------- -v name file (line+63)/64 D:\TAGS>tags -v junk.c C_entries junk.c 2 L_getit junk.c 3 PF_funcs junk.c 3 add_node junk.c 2 begtoken junk.c 1 concat junk.c 3 consider_token junk.c 2 endtoken junk.c 1 error junk.c 3 fatal junk.c 3 find_entries junk.c 2 free_tree junk.c 2 getit junk.c 3 getline junk.c 3 init junk.c 2 initbuffer junk.c 3 intoken junk.c 1 isgood junk.c 1 iswhite junk.c 1 main junk.c 2 max junk.c 1 pfnote junk.c 2 put_entries junk.c 2 readline junk.c 3 rindex junk.c 3 savestr junk.c 3 tail junk.c 3 takeprec junk.c 3 total_size_of_entries junk.c 2 xmalloc junk.c 3 xrealloc junk.c 3 D:\TAGS> ----------------------------------------------------------------------------- -x name line file definition D:\TAGS>tags -x junk.c C_entries 124 junk.c C_entries () L_getit 143 junk.c L_getit() PF_funcs 133 junk.c PF_funcs(fi) add_node 103 junk.c add_node(node, cur_node) begtoken 16 junk.c #define begtoken(arg) (_btk[arg]) concat 169 junk.c concat (s1, s2, s3) consider_token 126 junk.c consider_token (lpp, token, f) endtoken 18 junk.c #define endtoken(arg) (_etk[arg]) error 165 junk.c void error (s1, s2) fatal 162 junk.c fatal (s1, s2) find_entries 89 junk.c void find_entries (file) free_tree 100 junk.c void free_tree(node) getit 141 junk.c getit() getline 130 junk.c void getline (atchar) init 87 junk.c init() initbuffer 146 junk.c initbuffer (linebuffer) intoken 17 junk.c #define intoken(arg) (_itk[arg]) isgood 19 junk.c #define isgood(arg) (_gd[arg]) iswhite 15 junk.c #define iswhite(arg) (_wht[arg]) main 83 junk.c main(ac,av) max 20 junk.c #define max(I1,I2) (I1 > I2 ? I1 : I2) pfnote 92 junk.c pfnote (name, f, linestart, linelen, lno, cno) put_entries 106 junk.c put_entries(node) readline 150 junk.c readline (linebuffer, stream) rindex 159 junk.c rindex(sp, c) savestr 155 junk.c savestr(cp) tail 136 junk.c tail(cp) takeprec 139 junk.c takeprec() total_size_of_entries 109 junk.c int total_size_of_entries(node) xmalloc 173 junk.c xmalloc (size) xrealloc 177 junk.c xrealloc (ptr, size) If the -e flag is given, it dominates everything If the -e flag is not given and the -x flag is not given, we get search patterns If the -e flag is not given and the -v flag is not given, D:\TAGS>tags junk.c produces no output but writes the file 'tags': ^L junk.c,604 C_entries124,3669 L_getit143,3902 PF_funcs133,3809 add_node103,3310 #define begtoken16,428 concat169,4282 consider_token126,3687 #define endtoken18,561 void error165,4228 fatal162,4187 void find_entries89,3026 void free_tree100,3266 getit141,3889 void getline130,3763 init87,3014 initbuffer146,3923 #define intoken17,495 #define isgood19,624 #define iswhite15,368 main83,2965 #define max20,689 pfnote92,3074 put_entries106,3368 readline150,3995 rindex159,4140 savestr155,4099 tail136,3843 takeprec139,3873 int total_size_of_entries109,3413 xmalloc173,4341 xrealloc177,4390 ----------------------------------------------------------------------------- tags -t produces no output but writes file 'tags': C_entries junk.c /^C_entries ()$/ L_getit junk.c /^L_getit()$/ Mjunk junk.c /^main(ac,av)$/ NODE junk.c 32 PF_funcs junk.c /^PF_funcs(fi)$/ TYST junk.c 36 add_node junk.c /^add_node(node, cur_node)$/ begtoken junk.c /^#define begtoken(arg) (_btk[arg]) \ concat junk.c /^concat (s1, s2, s3)$/ consider_token junk.c /^consider_token (lpp, token, f)$/ endtoken junk.c /^#define endtoken(arg) (_etk[arg]) \ error junk.c /^void error (s1, s2)$/ fatal junk.c /^fatal (s1, s2)$/ find_entries junk.c /^void find_entries (file)$/ free_tree junk.c /^void free_tree(node)$/ getit junk.c /^getit()$/ getline junk.c /^void getline (atchar)$/ init junk.c /^init()$/ initbuffer junk.c /^initbuffer (linebuffer)$/ intoken junk.c /^#define intoken(arg) (_itk[arg]) \ isgood junk.c /^#define isgood(arg) (_gd[arg]) \ iswhite junk.c /^#define iswhite(arg) (_wht[arg]) \ max junk.c /^#define max(I1,I2) (I1 > I2 ? I1 : I2)$/ pfnote junk.c /^pfnote (name, f, linestart, linelen, lno, cno)$/ put_entries junk.c /^put_entries(node)$/ readline junk.c /^readline (linebuffer, stream)$/ rindex junk.c /^rindex(sp, c)$/ savestr junk.c /^savestr(cp)$/ tail junk.c /^tail(cp)$/ takeprec junk.c /^takeprec()$/ total_size_of_entries junk.c /^int total_size_of_entries(node)$/ xmalloc junk.c /^xmalloc (size)$/ xrealloc junk.c /^xrealloc (ptr, size)$/ ----------------------------------------------------------------------------- */ /***************************************************************************** * Change Log * Date | Change *-----------+----------------------------------------------------------------- * 2-Jun-86 | Created * 2-Jun-86 | reg => (nothing) since register variables don't buy all that * | much on an 80286, and most compilers have bugs with them * 2-Jun-86 | logical is now unsigned char * 2-Jun-86 | Added void to valueless procedures * 2-Jun-86 | replaced several 'register' declarations with 'reg' * 2-Jun-86 | xmalloc changed from int to char* so it works properly on * | 80286 and friends in all memory models * 2-Jun-86 | xmalloc takes unsigned arg, like malloc * 2-Jun-86 | xrealloc also changed consistent with xmalloc. Only problem * | is, I can't find out what realloc does... * 2-Jun-86 | include <> => include "" * 2-Jun-86 | Wrote a realloc routine which is sort of what is supposed * | to be done, I think * 2-Jun-86 | 'entry' => 'entry_name', reserved word in Lattice C * 2-Jun-86 | Added some forward (extern) declarations * 2-Jun-86 | Added explict case to return of readline * 2-Jun-86 | Looks like Lattice C has undocumented realloc function. * | Comment out my implementation * 2-Jun-86 | Added debug ctce and do some debug output * 3-Jun-86 | readline now returns the file position, not the number of chars * | read. Appropriate changes have been made at all sites that * | call readline. This is because of CRLF -> LF translation by * | C runtime. Note that Lattice C 3.0 provides correct file * | position even when translation is done; earlier Lattice C * | versions did not. * 3-Jun-86 | In C functions, pfnote adjusts endpos-1 (probably caused by * | above change) * 3-Jun-86 | Remove code for -u, did Unix system call; will need to rewrite * | later. * | Do wildcard expansions inline, since we can't assume that the * | commands are expanded; create new processing routine each_file * | which takes the filename as input and is the body of the loop * | formerly in 'main' * 3-Jun-86 | Quote patterns for Epsilon re_search * 3-Jun-86 | Changed pattern size from 50 to 40 due to apparent Epsilon * | limitation on pattern size for re-s * 3-Jun-86 | Expand wildcards by using Lattice C 3.0 functions dfind, dnext * | and strsfn (parses file names) * 4-Jun-86 | Always expand filename to full d:\path\file * 4-Jun-86 | Added code to support -u option * 4-Jun-86 | In changing name of 'main', search for \\, not / * 4-Jun-86 | Added Scribe/FinalWord mode * 4-Jun-86 | Annouce files and modes * 4-Jun-86 | Made duplicate tag message same format as standard error message * | so find-next-error in editor will find it * 4-Jul-86 | Added -m flag for makefile processing * 4-Jul-86 | Removed realloc code, no longer needed * 4-Jul-86 | Added TeX mode, not tested since I have no TeX files today * 4-Jul-86 | Added 'void' to various void procedures to a-void compiler * | warnings * 4-Jul-86 | Flush stderr when logging filenames *****************************************************************************/ #include "stdio.h" #include "ctype.h" #include "string.h" #include "dos.h" /* Define the symbol ETAGS to make the program "etags", which makes emacs-style tag tables by default. Define CTAGS to make the program "ctags" compatible with the usual one. Default is CTAGS = ETAGS = 0, which creates Epsilon tags. */ /* #define ETAGS 1 */ #define reg #define logical unsigned char #define debug FALSE #define TRUE (1) #define FALSE (0) #define iswhite(arg) (_wht[arg]) /* T if char is white */ #define begtoken(arg) (_btk[arg]) /* T if char can start token */ #define intoken(arg) (_itk[arg]) /* T if char can be in token */ #define endtoken(arg) (_etk[arg]) /* T if char ends tokens */ #define isgood(arg) (_gd[arg]) /* T if char can be after ')' */ #define max(I1,I2) (I1 > I2 ? I1 : I2) struct nd_st { /* sorting structure */ char *entry_name; /* function or type name */ char *file; /* file name */ logical f; /* use pattern or line no */ int lno; /* line number tag is on */ long cno; /* character number line starts on */ char *pat; /* search pattern */ logical been_warned; /* set if noticed dup */ struct nd_st *left,*right; /* left and right sons */ }; long ftell(); typedef struct nd_st NODE; int number; /* tokens found so far on line starting with # (including #) */ logical gotone, /* found a func already on line */ /* boolean "func" (see init) */ _wht[0177],_etk[0177],_itk[0177],_btk[0177],_gd[0177]; /* typedefs are recognized using a simple finite automata, * tydef is its state variable. */ typedef enum {none, begin, middle, end } TYST; TYST tydef = none; char searchar = '/'; /* use /.../ searches */ int lineno; /* line number of current line */ long charno; /* current character number */ long linecharno; /* character number of start of line */ char *curfile, /* current input file name */ *outfile= "tags", /* output file */ *white = " \f\t\n", /* white chars */ *endtk = " \t\n\"'#()[]{}=-+%*/&|^~!<>;,.:?", /* token ending chars */ *begtk = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz", /* token starting chars */ *intk = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz0123456789", /* valid in-token chars */ *notgd = ",;"; /* non-valid after-function chars */ int file_num; /* current file number */ int aflag; /* -a: append to tags */ int tflag; /* -t: create tags for typedefs */ int mflag; /* -m: create .tag file for makefile */ int uflag; /* -u: update tags */ int wflag; /* -w: suppress warnings */ int vflag; /* -v: create vgrind style index output */ int xflag; /* -x: create cxref style output */ int eflag; /* -e: emacs style output */ FILE *inf, /* ioptr for current input file */ *outf; /* ioptr for tags file */ NODE *head; /* the head of the sorted binary tree */ char *savestr(); char *rindex(); char *concat (); void initbuffer (); long readline (); /* A `struct linebuffer' is a structure which holds a line of text. `readline' reads a line from a stream into a linebuffer and works regardless of the length of the line. */ struct linebuffer { long size; char *buffer; }; struct linebuffer lb, lb1; int needCRLF = FALSE; #define forward extern forward void add_node(); forward void C_entries(); forward void checkCRLF(); forward void each_file(); forward void error(); forward void find_entries(); forward void free_tree(); forward void getit(); forward void getline(); forward void init(); forward void L_funcs(); forward void L_getit(); forward void put_entries(); forward void S_labels(); forward void tagit(); forward void takeprec(); forward void T_labels(); forward void usage(); forward char * xmalloc(); forward char * xrealloc(); forward char * realloc(); extern char * malloc(); extern char * stpchr(); /**************************************************************************** * main * Inputs: * int ac: count of arguments * char * av[]: vector of pointers to arguments * Result: void * Implicit value is returned via exit() call * Effect: * Executes the tags program ****************************************************************************/ void main(ac,av) int ac; char *av[]; { int i; #ifdef ETAGS eflag = 1; #endif while (ac > 1 && av[1][0] == '-') { eflag = 0; for (i=1; av[1][i]; i++) { switch(av[1][i]) { case 'B': searchar='?'; break; case 'F': searchar='/'; break; case 'a': aflag++; break; case 'e': eflag++; break; case 'm': mflag++; break; case 't': tflag++; break; case 'u': uflag++; break; case 'w': wflag++; break; case 'v': vflag++; xflag++; break; case 'x': xflag++; break; default: usage(av[0]); exit(1); } } ac--; av++; } if (ac <= 1) { usage(av[0]); exit(1); } if (eflag) outfile = "TAGS"; init(); /* set up boolean "functions" */ initbuffer (&lb); initbuffer (&lb1); /* * loop through files finding functions */ if (eflag) { outf = fopen (outfile, aflag ? "a" : "w"); if (!outf) { perror (outfile); exit (1); } } for (file_num = 1; file_num < ac; file_num++) { /* Unix would expand the file names; in MS-DOS if we have wildcards we have to expand them here. This requires doing the expansion and calling a per-file processing routine */ struct FILEINFO info; char fname[80]; char dev[3]; char path[FMSIZE]; char name[FNSIZE]; char ext[4]; int drive; if(dfind(&info,av[file_num],0)) { /* no files match */ fprintf(stderr,"%s 0 Error: Could not find file\n",av[file_num]); continue; } /* no files match */ /* Parse the filename into its device, path, filename and extension components */ strsfn(av[file_num],dev,path,name,ext); /* If no explicit path is given, get the current path */ if(strlen(dev) == 0) { /* default drive */ drive = getdsk() + 1; /* current default drive's path */ /* Note that A=0, B=1, etc., but we want it encoded as A=1, B=2, etc. */ dev[0] = drive + 'A' - 1; dev[1] = ':'; dev[2] = '\0'; } /* default drive */ else drive = dev[0] - 'A' + 1; if(strlen(path) == 0) getcd(drive,path); do { /* scan file name */ /* Expand the filename by getting the device and path from the input string and appending the filename from the info block */ strcpy(fname,dev); if(strlen(path) > 0 && path[0] != '\\') strcat(fname,"\\"); strcat(fname,path); if(fname[strlen(fname)-1] != '\\') strcat(fname,"\\"); strcat(fname,info.name); strlwr(fname); /* make all lower case for readability */ if(!xflag && !vflag) { /* announce */ fprintf(stderr,"%s",fname); fflush(stderr); needCRLF = TRUE; } /* announce */ each_file(fname); checkCRLF(); } /* scan file name */ while (!dnext(&info)); } if (uflag) { /* This sequence can be omitted because we sort the listing when we are in Epsilon */ /**************************************************************** sprintf(cmd, "sort %s -o %s", outfile, outfile); system(cmd); ****************************************************************/ } exit(0); } /**************************************************************************** * usage * Inputs: * char * name: Program name from argv[0] * Result: void * * Effect: * Issues the 'usage' message * Notes: * This depends upon the fact that DOS 3.x returns the program name as * the first argument. If it does not, you will have to change this * routine ****************************************************************************/ void usage(name) char * name; { char node[20]; char drive[4]; char path[80]; char ext[5]; strsfn(name,drive,path,node,ext); fprintf(stderr,"Usage: %s [-BFaemtuwvx] file ...\n",node); } /**************************************************************************** * checkCRLF * Effect: * If a CRLF is needed on the output stream, puts one out. Sets * needCRLF to false ****************************************************************************/ void checkCRLF() { if(needCRLF) fprintf(stderr,"\n"); needCRLF = FALSE; } /**************************************************************************** * strip_out * Inputs: * char * file: file name to strip out (note that it must be in the * same form as the one in the file) * * Result: boolean * true if operation was successful * false if it failed for some reason * Effect: * renames "tags" to "otags", copies it back to "tags" stripping out * entries for the current file ****************************************************************************/ int strip_out(file) char * file; { FILE * old; FILE * new; fprintf(stderr,"stripping out old entries..."); fflush(stderr); needCRLF = TRUE; remove("otags"); if(rename("tags","otags")) { /* failed */ fprintf(stderr,"Unable to rename 'tags' to 'otags', update cannot be done\n"); return FALSE; } /* failed */ old = fopen("otags","r"); if(old==NULL) { /* failed */ fprintf(stderr,"failed to open 'otags' for copy, update cannot be done\n"); return FALSE; } /* failed */ new = fopen(outfile,"w"); if(new==NULL) { /* failed again */ fprintf(stderr,"failed to open '%s' for copy, update cannot be done\n", outfile); close(old); return FALSE; } /* failed again */ while(TRUE) { /* copy lines */ char line[256]; char * p1; char * p2; fgets(line,256,old); if(feof(old)) break; p1 = stpchr(line,'\t'); /* find initial tab char */ if(p1 != NULL) { /* ok */ p1++; p2 = stpchr(p1,'\t'); /* find next tab char */ if(p2 != NULL) { /* found it! */ if(strncmp(p1,file,p2-p1) == 0) { /* strip it! */ continue; } /* strip it! */ } /* found it! */ } /* ok */ fputs(line,new); } /* copy lines */ fclose(old); fclose(new); printf("\n"); return TRUE; } /**************************************************************************** * each_file * Inputs: * char * file: File name to process * Result: void * * Effect: * Processes the file. ****************************************************************************/ void each_file(file) char * file; { find_entries(file); if (eflag) { fprintf (outf, "\f\n%s,%d\n", file, total_size_of_entries (head)); put_entries (head); free_tree (head); head = NULL; } if (eflag) { fclose (outf); } if (xflag) { put_entries(head); } if (uflag) { /* The purpose of the following sequence is to copy everything from the original output file to a file of the same name, eliminating all of the information of the current file under consideration. In Unix, this is done by using fgrep to remove all but the strings of the current file name. In MS-DOS, we have to be much, much sneakier, but I'm not going to write that code right now. */ /***************************************************************** sprintf(cmd, "mv %s OTAGS;fgrep -v '\t%s\t' OTAGS >%s;rm OTAGS", outfile, file, outfile); system(cmd); ****************************************************************/ if(strip_out(file)) aflag++; else return; } /* If we are in update mode, we open for append access (having just eliminated the data for our current file in the section above if necessary); otherwise we overwrite the output file If we have already processed one file, we also have incremented aflag, so we will be in update mode also. */ outf = fopen(outfile, aflag ? "a" : "w"); if (outf == NULL) { /* output open failed */ perror(outfile); exit(1); } /* output open failed */ put_entries(head); fclose(outf); } /**************************************************************************** * init * Effect: * This routine sets up the boolean psuedo-functions which work * by seting boolean flags dependent upon the corresponding character * Every char which is NOT in that string is not a white char. * Therefore, all of the array "_wht" is set to FALSE, and then the * elements subscripted by the chars in "white" are set to TRUE. Thus * "_wht" of a char is TRUE if it is the string "white", else FALSE. ****************************************************************************/ void init() { reg char *sp; reg int i; for (i = 0; i < 0177; i++) { _wht[i] = _etk[i] = _itk[i] = _btk[i] = FALSE; _gd[i] = TRUE; } for (sp = white; *sp; sp++) _wht[*sp] = TRUE; for (sp = endtk; *sp; sp++) _etk[*sp] = TRUE; for (sp = intk; *sp; sp++) _itk[*sp] = TRUE; for (sp = begtk; *sp; sp++) _btk[*sp] = TRUE; for (sp = notgd; *sp; sp++) _gd[*sp] = FALSE; _wht[0] = _wht['\n']; _etk[0] = _etk['\n']; _btk[0] = _btk['\n']; _itk[0] = _itk['\n']; _gd[0] = _gd['\n']; } /**************************************************************************** * tagit * Inputs: * char * file: Name of file * Result: void * * Effect: * Writes a .tag file of the same name as the input file; this is * for use with makefiles so that tags can be automatically updated * (note that recompilation should not affect tag regeneration, since * it may be that the source is unchanged but a dependent file is * changed) ****************************************************************************/ void tagit(file) char * file; { char tagname[80]; char * p; FILE * tagfile; strcpy(tagname,file); p = rindex(tagname,'.'); if(p!=NULL) *p = '\0'; strcat(tagname,".tag"); tagfile = fopen(tagname,"w"); if(tagfile == NULL) { /* failed */ fprintf(stderr,"Failed to create \"%s\", auto-retagging may file in your makefile\n",tagfile); return; } /* failed */ fclose(tagfile); return; } /**************************************************************************** * find_entries * Inputs: * char * file * Result: void * * Effect: * This routine opens the specified file and calls the function * which finds the function and type definitions. ****************************************************************************/ void find_entries (file) char *file; { char *cp; if ((inf=fopen(file,"r")) == NULL) { perror(file); return; } curfile = savestr(file); cp = rindex(file, '.'); /* .l or .el or .lisp implies lisp source code */ /* LISP MODE FILE */ if (cp && (!strcmp (cp + 1, "l") || !strcmp (cp + 1, "el") || !strcmp (cp + 1, "lisp"))) { L_funcs(inf); fclose(inf); if(mflag) tagit(file); return; } /* If a .MSS file, try Scribe mode */ if (cp && (strcmp(cp+1,"mss") == 0)) { /* scribe */ S_labels(inf); fclose(inf); if(mflag) tagit(file); return; } /* scribe */ /* If a .TEX file, try TeX mode */ if (cp && (strcmp(cp+1,"tex") == 0)) { /* TeX */ T_labels(inf); fclose(inf); if(mflag) tagit(file); return; } /* TeX */ /* if not a .c or .h or .y file, try fortran */ if (cp && (cp[1] != 'c' && cp[1] != 'h' && cp[1] != 'y') && cp[2] == '\0') { if (PF_funcs(inf) != 0) { fclose(inf); if(mflag) tagit(file); return; } rewind(inf); /* no fortran tags found, try C */ } C_entries(); fclose(inf); if(mflag) tagit(file); } /**************************************************************************** * pfnote * Inputs: * char * name: * logical f: * char * linestart: pointer to start of line * int linelen: length of line in characters * int lno: Number of line in input file * long cno: Character number in input file * Result: void * * Effect: * Record a tag on the current line. * 'name' is the tag name, * 'f' is nonzero to use a pattern, zero to use line number instead. ****************************************************************************/ void pfnote (name, f, linestart, linelen, lno, cno) char *name; logical f; /* f == TRUE when function */ char *linestart; int linelen; int lno; long cno; { reg char *fp; reg NODE *np; char *altname; char tem[51]; #if debug printf("pfnote(\"%s\",%d,\"%s\",%d,%d,%ld)\n", name,f,linestart,linelen,lno,cno); #endif if ((np = (NODE *) malloc(sizeof (NODE))) == NULL) { fprintf(stderr, "ctags: too many entries to sort\n"); put_entries(head); free_tree(head); head = NULL; np = (NODE *) xmalloc(sizeof (NODE)); } /* Change name "main" to M. */ if (!eflag && !xflag && !strcmp(name, "main")) { fp = rindex(curfile, '\\'); if (fp == 0) fp = curfile; else fp++; altname = concat ("M", fp, ""); fp = rindex(altname, '.'); if (fp && fp[2] == 0) *fp = 0; name = altname; } np->entry_name = savestr(name); np->file = curfile; np->f = f; np->lno = lno; np->cno = cno; np->left = np->right = 0; if (eflag) { linestart[linelen] = 0; } else if (xflag == 0) { sprintf (tem, strlen (linestart) < 40 ? "%s$" : "%.40s", linestart); linestart = tem; } np->pat = savestr (linestart); if (head == NULL) head = np; else add_node(np, head); } /**************************************************************************** * free_tree * Inputs: * NODE * node: Root of tree to free * Result: void * * Effect: * Releases all the storage in the tree ****************************************************************************/ void free_tree(node) NODE *node; { while (node) { free_tree(node->right); free(node); node = node->left; } } /**************************************************************************** * add_node * Inputs: * NODE * node: Node to add * NODE * cur_node: Node at which we are currently sitting * Result: void * * Effect: * Adds the node into the tree as a descendent of cur_node ****************************************************************************/ void add_node(node, cur_node) NODE *node,*cur_node; { register int dif; dif = strcmp(node->entry_name, cur_node->entry_name); /* If this tag name matches an existing one, then unless -e was given, do not add the node, but maybe print a warning */ if (!eflag && !dif) { if (node->file == cur_node->file) { if (!wflag) { checkCRLF(); fprintf(stderr,"%s %d Warning: Duplicate entry \"%s\"\n", node->file,lineno,node->entry_name); fprintf(stderr,"Second entry ignored\n"); } return; } if (!cur_node->been_warned) if (!wflag) { /* print warning */ checkCRLF(); fprintf(stderr,"Duplicate entry in files %s and %s: %s (Warning only)\n", node->file, cur_node->file, node->entry_name); } /* print warning */ cur_node->been_warned = TRUE; return; } /* Actually add the node */ if (dif < 0) { if (cur_node->left != NULL) add_node(node,cur_node->left); else cur_node->left = node; return; } if (cur_node->right != NULL) add_node(node,cur_node->right); else cur_node->right = node; } /**************************************************************************** * put_entries * Inputs: * NODE * node: * Result: void * * Effect: * 2 ****************************************************************************/ void put_entries(node) reg NODE *node; { reg char *sp; if (node == NULL) return; /* Output subentries that precede this one */ put_entries (node->left); /* Output this entry */ if (eflag) { fprintf (outf, "%s%c%d,%d\n", node->pat, 0177, node->lno, node->cno); } else if (!xflag) { fprintf (outf, "%s\t%s\t", node->entry_name, node->file); if (node->f) { /* a function */ putc (searchar, outf); putc ('^', outf); for (sp = node->pat; *sp; sp++) { if ( /* Epsilon re_search chars quoted */ *sp == searchar || *sp == '*' || *sp == '%' || *sp == '^' || *sp == '.' || *sp == '[' || *sp == ']' || *sp == '(' || *sp == ')' || *sp == '+' || *sp == '|' || *sp == '!') putc ('%', outf); /* put out quoting char */ putc (*sp, outf); } putc (searchar, outf); } else { /* a typedef; text pattern inadequate */ fprintf (outf, "%d", node->lno); } putc ('\n', outf); } else if (vflag) fprintf (stdout, "%s %s %d\n", node->entry_name, node->file, (node->lno+63)/64); else fprintf (stdout, "%-16s%4d %-16s %s\n", node->entry_name, node->lno, node->file, node->pat); /* Output subentries that follow this one */ put_entries (node->right); } /**************************************************************************** * total_size_of_entries * Inputs: * NODE * node: root of subtree * Result: int * Return total number of characters that put_entries will output for * the nodes in the subtree of the specified node. * Works only if eflag is set, but called only in that case. ****************************************************************************/ int total_size_of_entries(node) reg NODE *node; { reg int total = 0; reg long num; if (node == NULL) return 0; /* Count subentries that precede this one */ total = total_size_of_entries (node->left); /* Count subentries that follow this one */ total += total_size_of_entries (node->right); /* Count this entry */ total += strlen (node->pat) + 3; num = node->lno; while (num) { total++; num /= 10; } num = node->cno; if (!num) total++; while (num) { total++; num /= 10; } return total; } /**************************************************************************** * CNL_SAVE_NUMBER * Effect: * 2 ****************************************************************************/ #define CNL_SAVE_NUMBER \ { \ linecharno = charno; lineno++; \ charno = readline (&lb, inf); \ lp = lb.buffer; \ } /**************************************************************************** * CNL * Effect: * 2 ****************************************************************************/ #define CNL \ { \ CNL_SAVE_NUMBER; \ number = 0; \ } /**************************************************************************** * C_entries * Result: 2 * 2 * Effect: * This routine finds functions and typedefs in C syntax and adds them * to the list. ****************************************************************************/ void C_entries () { reg int c; reg char *token, *tp, *lp; logical incomm, inquote, inchar, midtoken; int level; char tok[BUFSIZ]; token = NULL; /* avoid uninitialized var errors, even though flow of control makes it impossible*/ tp = NULL; /* likewise */ fprintf(stderr," C mode "); fflush(stderr); needCRLF = TRUE; lineno = 0; charno = 0; lp = lb.buffer; *lp = 0; number = 0; gotone = midtoken = inquote = inchar = incomm = FALSE; level = 0; while (!feof (inf)) { c = *lp++; if (c == 0) { CNL; gotone = FALSE; } if (c == '\\') { c = *lp++; if (c == 0) CNL_SAVE_NUMBER; c = ' '; } else if (incomm) { if (c == '*') { while ((c = *lp++) == '*') continue; if (c == 0) CNL; if (c == '/') incomm = FALSE; } } else if (inquote) { /* * Too dumb to know about \" not being magic, but * they usually occur in pairs anyway. */ if (c == '"') inquote = FALSE; continue; } else if (inchar) { if (c == '\'') inchar = FALSE; continue; } else switch (c) { /* decode char */ case '"': inquote = TRUE; continue; case '\'': inchar = TRUE; continue; case '/': if (*lp == '*') { lp++; incomm = TRUE; } continue; case '#': if (lp == lb.buffer + 1) number = 1; continue; case '{': if (tydef == begin) { tydef=middle; } level++; continue; case '}': if (lp == lb.buffer + 1) level = 0; /* reset */ else level--; if (!level && tydef==middle) { tydef=end; } continue; } /* decode char */ if (!level && !inquote && !incomm && gotone == FALSE) { if (midtoken) { if (endtoken(c)) { int f; char *buf = lb.buffer; int endpos = lp - lb.buffer; char *lp1 = lp; int line = lineno; long linestart = linecharno; int tem = consider_token (&lp1, token, &f); lp = lp1; if (tem) { if (linestart != linecharno) { getline (linestart); strncpy (tok, token + (lb1.buffer - buf), tp-token+1); tok[tp-token+1] = 0; pfnote(tok, f, lb1.buffer, endpos - 1, line, linestart); } else { strncpy (tok, token, tp-token+1); tok[tp-token+1] = 0; pfnote(tok, f, lb.buffer, endpos - 1, line, linestart); } gotone = f; /* function */ } midtoken = FALSE; token = lp - 1; } else if (intoken(c)) tp++; } else if (begtoken(c)) { token = tp = lp - 1; midtoken = TRUE; } } if (c == ';' && tydef==end) /* clean with typedefs */ tydef=none; } } /**************************************************************************** * consider_token * Inputs: * char ** lpp: Pointer to line position pointer * char * token: Pointer to token buffer * int * f: * Result: boolean * 2 * Effect: * This routine checks to see if the current token is * at the start of a function, or corresponds to a typedef * It updates the input line * 'lpp' so that the '(' will be * in it when it returns. ****************************************************************************/ consider_token (lpp, token, f) char **lpp, *token; int *f; { reg char *lp = *lpp; reg char c; static logical next_token_is_func; logical firsttok; /* T if have seen first token in ()'s */ int bad, win; #if debug printf("consider_token(0x%x:\"%s\",0x%x,0x%x)\n",lpp,*lpp,token,f); #endif *f = 1; /* a function */ c = lp[-1]; bad = FALSE; if (!number) { /* space is not allowed in macro defs */ while (iswhite(c)) { c = *lp++; if (c == 0) { if (feof (inf)) break; CNL; } } /* the following tries to make it so that a #define a b(c) */ /* doesn't count as a define of b. */ } else { number++; if (number >= 4 || (number==2 && strncmp (token, "define", 6))) { gotone = TRUE; badone: bad = TRUE; goto ret; } } /* check for the typedef cases */ if (tflag && !strncmp(token, "typedef", 7)) { tydef=begin; goto badone; } if (tydef==begin && (!strncmp(token, "struct", 6) || !strncmp(token, "union", 5) || !strncmp(token, "enum", 4))) { goto badone; } if (tydef==begin) { tydef=end; goto badone; } if (tydef==end) { *f = 0; win = 1; goto ret; } /* Detect GNUmacs's function-defining macros. */ if (!number && !strncmp (token, "DEF", 3)) { next_token_is_func = 1; goto badone; } if (next_token_is_func) { next_token_is_func = 0; win = 1; goto ret; } if (c != '(') goto badone; firsttok = FALSE; while ((c = *lp++) != ')') { if (c == 0) { if (feof (inf)) break; CNL; } /* * This line used to confuse ctags: * int (*oldhup)(); * This fixes it. A nonwhite char before the first * token, other than a / (in case of a comment in there) * makes this not a declaration. */ if (begtoken(c) || c=='/') firsttok++; else if (!iswhite(c) && !firsttok) goto badone; } while (iswhite (c = *lp++)) { if (!c) CNL; } win = isgood (c); ret: *lpp = lp - 1; return !bad && win; } /**************************************************************************** * getline * Inputs: * long atchar: character at which to start scan * Result: void * * Effect: * Reads an input line starting at 'atchar' and proceeding thru the * next linefeed character ****************************************************************************/ void getline (atchar) long atchar; { long saveftell = ftell (inf); #if debug printf("getline(%ld): saveftell = %ld",atchar,saveftell); #endif fseek (inf, atchar, 0); readline (&lb1, inf); #if debug printf(" => \"%s\"\n",lb.buffer); #endif fseek (inf, saveftell, 0); } /* Fortran parsing */ char *dbp; int pfcnt; PF_funcs(fi) FILE *fi; { lineno = 0; charno = 0; pfcnt = 0; while (!feof (fi)) { lineno++; linecharno = charno; charno = readline (&lb, fi); dbp = lb.buffer; if (*dbp == '%') dbp++ ; /* Ratfor escape to fortran */ while (isspace(*dbp)) dbp++; if (*dbp == 0) continue; switch (*dbp |' ') { case 'i': if (tail("integer")) takeprec(); break; case 'r': if (tail("real")) takeprec(); break; case 'l': if (tail("logical")) takeprec(); break; case 'c': if (tail("complex") || tail("character")) takeprec(); break; case 'd': if (tail("double")) { while (isspace(*dbp)) dbp++; if (*dbp == 0) continue; if (tail("precision")) break; continue; } break; } while (isspace(*dbp)) dbp++; if (*dbp == 0) continue; switch (*dbp|' ') { case 'f': if (tail("function")) getit(); continue; case 's': if (tail("subroutine")) getit(); continue; case 'p': if (tail("program")) { getit(); continue; } if (tail("procedure")) getit(); continue; } } return (pfcnt); } int tail(cp) char *cp; { register int len = 0; while (*cp && (*cp&~' ') == ((*(dbp+len))&~' ')) cp++, len++; if (*cp == 0) { dbp += len; return (1); } return (0); } void takeprec() { while (isspace(*dbp)) dbp++; if (*dbp != '*') return; dbp++; while (isspace(*dbp)) dbp++; if (!isdigit(*dbp)) { --dbp; /* force failure */ return; } do dbp++; while (isdigit(*dbp)); } /**************************************************************************** * getit * Result: void * * Effect: * 2 ****************************************************************************/ void getit() { register char *cp; char c; char nambuf[BUFSIZ]; while (isspace(*dbp)) dbp++; if (*dbp == 0 || !isalpha(*dbp)) return; for (cp = dbp+1; *cp && (isalpha(*cp) || isdigit(*cp)); cp++) continue; c = cp[0]; cp[0] = 0; strcpy(nambuf, dbp); cp[0] = c; pfnote(nambuf, TRUE, lb.buffer, cp - lb.buffer + 1, lineno, linecharno); pfcnt++; } /**************************************************************************** * get_bkt * Inputs: * char * bp: Bracket pointer to start of bracketed string (may have * leading whitespace) * Result: char * * Bracket pointer to ending bracket ****************************************************************************/ char * get_bkt(bp) char * bp; { char * ebp; char ebkt; /* skip initial whitespace */ while(*bp == ' ') bp++; switch(*bp) { /* find match */ #define mbkt(l,r) case l: ebkt = r; break mbkt('[',']'); mbkt('(',')'); mbkt('<','>'); mbkt('{','}'); mbkt('\"','\"'); default: ebkt = NULL; } /* find match */ if(!ebkt) return NULL; ebp = stpchr(++bp,ebkt); return ebp; } /**************************************************************************** * S_labels * Inputs: * FILE * fi: Input file * Effect: * Scans for @tag and @label directives ****************************************************************************/ void S_labels(fi) FILE * fi; { char nambuf[BUFSIZ]; char * ebp; char * dbp; char * obp; char * cp; fprintf(stderr," Scribe mode "); fflush(stderr); needCRLF = TRUE; lineno = 0; charno = 0; pfcnt = 0; while (!feof(fi)) { /* read lines */ lineno++; linecharno = charno; charno = readline(&lb,fi); dbp = lb.buffer; while( dbp = stpchr(dbp,'@')) { /* some Scribe command... */ ebp = NULL; if(strncmp(dbp,"@label",6) == 0) { /* @label */ obp = &dbp[6]; while(*obp == ' ') obp++; ebp = get_bkt(obp); if(ebp != NULL) { /* parse label */ char c; /* @label < name > * ^dbp ^ebp * ^obp */ while (*++obp == ' ') ; c = *ebp; *ebp = '\0'; strcpy(nambuf,obp); *ebp = c; /* trim trailing whitespace */ while(cp = rindex(nambuf,' ')) *cp = '\0'; pfnote(nambuf, TRUE, dbp, ebp - dbp + 1, lineno, linecharno); pfcnt++; } /* parse label */ } /* @label */ else if(strncmp(dbp,"@tag",4) == 0) { /* @tag */ obp = &dbp[4]; /* @tag < name > * ^dbp * ^obp */ while(*obp == ' ') obp++; /* @tag < name > * ^dbp * ^obp */ ebp = get_bkt(obp); if(ebp != NULL) { /* parse tag */ char c; /* @tag < name > * ^dbp ^ebp * ^obp */ while( *++obp == ' ') ; /* @tag < name > * ^dbp ^ebp * ^obp */ c = *ebp; *ebp = '\0'; strcpy(nambuf,obp); *ebp = c; /* It may be of the form @tag < name = value > so nambuf contains name = value */ if(cp = stpchr(nambuf,'=')) { /* equality */ *cp = '\0'; } /* equality */ /* trim trailing whitespace */ while(cp = rindex(nambuf,' ')) *cp = '\0'; pfnote(nambuf, TRUE, dbp, ebp - dbp + 1, lineno, linecharno); pfcnt++; } /* parse tag */ } /* @tag */ dbp++; } /* some Scribe command... */ } /* read lines */ } /**************************************************************************** * T_labels * Inputs: * FILE * fi: Input file * Effect: * Scans for \label directives ****************************************************************************/ void T_labels(fi) FILE * fi; { char nambuf[BUFSIZ]; char * ebp; char * dbp; char * obp; char * cp; fprintf(stderr," TeX mode "); fflush(stderr); needCRLF = TRUE; lineno = 0; charno = 0; pfcnt = 0; while (!feof(fi)) { /* read lines */ lineno++; linecharno = charno; charno = readline(&lb,fi); dbp = lb.buffer; while( dbp = stpchr(dbp,'\\')) { /* some TeX command... */ ebp = NULL; if(strncmp(dbp,"\\label",6) == 0) { /* \label */ obp = &dbp[6]; while(*obp == ' ') obp++; ebp = obp; while(*ebp != '\n' && *ebp != ' ') ebp++; if(ebp != NULL) { /* parse label */ char c; /* \label name * ^dbp ^ebp * ^obp */ c = *ebp; *ebp = '\0'; strcpy(nambuf,obp); *ebp = c; /* trim trailing whitespace */ while(cp = rindex(nambuf,' ')) *cp = '\0'; pfnote(nambuf, TRUE, dbp, ebp - dbp + 1, lineno, linecharno); pfcnt++; } /* parse label */ } /* \label */ dbp++; } /* some TeX command... */ } /* read lines */ } /* * lisp tag functions * just look for (def or (DEF */ void L_funcs (fi) FILE *fi; { lineno = 0; charno = 0; pfcnt = 0; while (!feof (fi)) { lineno++; linecharno = charno; charno = readline (&lb, fi); dbp = lb.buffer; if (dbp[0] == '(' && (dbp[1] == 'D' || dbp[1] == 'd') && (dbp[2] == 'E' || dbp[2] == 'e') && (dbp[3] == 'F' || dbp[3] == 'f')) { while (!isspace(*dbp)) dbp++; while (isspace(*dbp)) dbp++; L_getit(); } } } void L_getit() { register char *cp; char c; char nambuf[BUFSIZ]; if (*dbp == 0) return; for (cp = dbp+1; *cp && *cp != '(' && *cp != ' '; cp++) continue; c = cp[0]; cp[0] = 0; strcpy(nambuf, dbp); cp[0] = c; pfnote(nambuf, TRUE, lb.buffer, cp - lb.buffer + 1, lineno, linecharno); pfcnt++; } /**************************************************************************** * initbuffer * Inputs: * struct linebuffer * linebuffer: Line buffer structure to be * initialized * Result: void * * Effect: * initializes a line buffer for use ****************************************************************************/ void initbuffer (linebuffer) struct linebuffer *linebuffer; { linebuffer->size = 200; linebuffer->buffer = (char *) xmalloc (200); } /**************************************************************************** * readline * Inputs: * struct linebuffer * linebuffer: Line buffer to hold input line * FILE * stream: file from which to read * Result: long * file position of end of read operation * Effect: * Read a line of text from `stream' into `linebuffer'. * Return the length of the line. ****************************************************************************/ long readline (linebuffer, stream) struct linebuffer *linebuffer; reg FILE *stream; { char *buffer = linebuffer->buffer; reg char *p = linebuffer->buffer; reg char *pend = p + linebuffer->size; while (1) { int c = getc (stream); if (p == pend) { buffer = (char *) xrealloc (buffer, linebuffer->size *= 2); p += buffer - linebuffer->buffer; pend += buffer - linebuffer->buffer; linebuffer->buffer = buffer; } /* If we have a NUL or LF, store a NUL at the end of the buffer and exit the loop */ if (c < 0 || c == '\n') { *p = 0; break; } /* Otherwise, store the character and update the pointer */ *p++ = c; } #if debug printf("readline: |%s| => %ld\n", buffer, (long) (p-buffer)); #endif return ftell( stream); } /**************************************************************************** * savestr * Inputs: * char * cp: Pointer to string * Result: char * * pointer to copy of the string originally pointed to by cp * Effect: * Creates a copy of the string pointed to by 'cp' ****************************************************************************/ char * savestr(cp) char *cp; { register int len; register char *dp; len = strlen(cp); dp = (char *)xmalloc(len+1); strcpy(dp, cp); return (dp); } /**************************************************************************** * rindex * Inputs: * char * sp: Pointer to string * char c: Character of interest * Result: char * * Pointer to last instance of c in sp, or NULL if no occurrence * * Identical to v7 rindex, included for portability. ****************************************************************************/ char * rindex(sp, c) register char *sp, c; { register char *r; r = NULL; do { if (*sp == c) r = sp; } while (*sp++); return(r); } /**************************************************************************** * fatal * Inputs: * char * s1: Printf control string with one %-arg * char * s2: Argument for printf control string * Result: never returns * * Effect: * Issues error message and exits with halt code 1 ****************************************************************************/ void fatal (s1, s2) char *s1, *s2; { error (s1, s2); exit (1); } /**************************************************************************** * error * Inputs: * char * s1: Printf control string with one %-arg * char * s2: Argument for printf control string * Result: void * * Effect: * Issues error message followed by newline ****************************************************************************/ void error (s1, s2) char *s1, *s2; { #ifdef CTAGS printf ("ctags: "); #else printf ("etags: "); #endif printf (s1, s2); printf ("\n"); } /**************************************************************************** * concat * Inputs: * char * s1, *s2, *s3: Three string pointers * Result: char * * New string which is concatenation of s1, s2, s3 * Effect: * Allocates new string and copies s1, s2, s3 into it ****************************************************************************/ char * concat (s1, s2, s3) char *s1, *s2, *s3; { int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3); char *result = (char *) xmalloc (len1 + len2 + len3 + 1); strcpy (result, s1); strcpy (result + len1, s2); strcpy (result + len1 + len2, s3); *(result + len1 + len2 + len3) = 0; return result; } /**************************************************************************** * xmalloc * Inputs: * unsigned size: Size of storage to allocate * Result: char * * Newly allocated storage. * Effect: * Like malloc, except takes fatal error and exits if memory exhausted ****************************************************************************/ char * xmalloc (size) unsigned size; { char * result = malloc (size); if (!result) fatal ("virtual memory exhausted", 0); return result; } /**************************************************************************** * xrealloc * Inputs: * char * ptr: Pointer to space containing information * unsigned size: Size we really want * Result: 2 * 2 * Effect: * like realloc but gives fatal error and exits if memory exhausted * * jmn: I can't find this anywhere in my Unix manuals or Lattice C * manual, but judging from its use it is supposed to allocate * a chunk of storage of size 'size' and return a pointer to it. * The presumption is that if it can tail-allocate or otherwise * avoid copying it will do so. I have written a realloc for * MS-DOS which has to copy ****************************************************************************/ char * xrealloc (ptr, size) char *ptr; unsigned size; { char * result = realloc (ptr, size); if (!result) fatal ("virtual memory exhausted"); return result; } /* The text which follows is the Epsilon Tags.e file. Extract it and use it with Epsilon */ #if 0 /***************************************************************************** * Change Log * Date | Change *-----------+----------------------------------------------------------------- * 3-Jun-86 | Created * 4-Jun-86 | Use absolute for tagname in load_tags * 4-Jun-86 | [313.43] Made non-resident; copied goodies to tags1 *****************************************************************************/ #include "eel.h" #include "boolean.h" boolean tags_loaded = false; /**************************************************************************** * tags * Effect: * Loads and executes the tags library ****************************************************************************/ command tags() on reg_tab[ALT('.')] { if(!tags_loaded) { /* load it */ load_library("tags1"); tags_loaded = true; tags_1(); } /* load it */ else tags_1(); } /* End of TAGS.E */ #endif /* The following file is the Epsilon code for TAGS1.E. Extract it and use it with Epsilon */ #if 0 /***************************************************************************** * Change Log * Date | Change *-----------+----------------------------------------------------------------- * 3-Jun-86 | Created * 4-Jun-86 | Use absolute for tagname in load_tags * 4-Jun-86 | [313.43] Made nonresident; entry point is tags_1 * 10-Jul-86 | [313.46] Restore point after grab of search string * 10-Jul-86 | [313.46] Make sure all 'error' calls after reading in tags * | properly restore oldbuf and point *****************************************************************************/ /* Parse the tags file looking for the input name; position the user in that file looking at the name. Limitations: If the filename in the tags file is not a full path, it is interpreted relative to the current connected directory. */ #include "eel.h" #include "boolean.h" #define debug_tags false /**************************************************************************** * debug_tags_msg * Inputs: * char * msg: Message to print * Effect: * Puts it in the debug buffer ****************************************************************************/ #if debug_tags debug_tags_msg(msg) char * msg; { char * oldbuf = bufname; if(!exist("debug")) { /* make debug buffer */ zap("debug"); } /* make debug buffer */ bufname = "debug"; point = size(); stuff(msg); bufname = oldbuf; } #endif /**************************************************************************** * load_tags * Result: boolean * true if tags file found * false if tags file not found * Effect: * loads the tags file ****************************************************************************/ boolean load_tags() { char * s; char tagname[80]; s = buffer_list(1); do { /* see if buffer exists */ bufname = s; if(strcmp(filename,"tags")==0) { /* found it */ /* What we do here is see if the file timestamp is the same; if not, we reload the tags file */ struct file_info f_disk; struct time_info tf, tb; if(check_file("tags",&f_disk)) { /* lost tags */ /* This is probably because we have shifted to another directory, or tags failed after partially erasing the old file, so what we do here is assume the 'tags' buffer we are in is valid */ #if debug_tags debug_tags_msg("load_tags: lost tags file, use existing one\n"); #endif return true; } /* lost tags */ if(f_disk.year == f_info.year && f_disk.month == f_info.month && f_disk.day == f_info.day && f_disk.hour == f_info.hour && f_disk.minute == f_info.minute && f_disk.second == f_info.second) { /* same file */ #if debug_tags debug_tags_msg("load_tags: tags file is the same\n"); #endif return true; } /* same file */ /* At this point, the timestamp of 'tags' on disk and the timestamp of 'tags' in the buffer are different */ #if debug_tags { /* reverting */ char msg[80]; sprintf(msg, "load_tags: reverted 'tags' file. bufname = %s\n", bufname); debug_tags_msg(msg); } /* reverting */ #endif read_file("tags"); point = 0; return true; } /* found it */ } /* see if buffer exists */ while (s = buffer_list(0)); /* We didn't find a tags buffer; create one and try to read in a file called 'tags' */ zap("tags"); bufname = "tags"; strcpy(tagname,"tags"); absolute(tagname); read_file(tagname); #if debug_tags debug_tags_msg("load_tags: created 'tags' buffer and loaded it\n"); #endif return true; } /**************************************************************************** * locate_pattern * Inputs: * char * fname: File name in which to locate pattern * char * pattern: Pattern to find * Result: boolean * true if pattern found * false if pattern not found * Effect: * true: * Reads the file into a buffer if required. * Leaves the buffer positioned at the line containing the pattern * false: * Reads the file into a buffer if required. * Leaves the buffer unchanged (at start if just read in) ****************************************************************************/ locate_pattern(fname,pattern) char * fname; char * pattern; { int oldpoint; char * oldbuf = bufname; int orig_num = window_number; window_number = 0; /* We search to see if the file is displayed in any window; in that case we want to go to the window to find it */ do { /* search for currently on screen */ if(strcmp(filename,fname) == 0) orig_num = window_number; window_number++; } /* search for currently on screen */ while (window_number); window_number = orig_num; #if debug_tags { /* log it */ char msg[120]; sprintf(msg,"locate_pattern(\"%s\",\"%s\")\n",fname,pattern); debug_tags_msg(msg); } /* log it */ #endif find_it(fname); oldpoint = point; point = 0; if(!re_search(FORWARD,pattern)) { /* failed */ point = oldpoint; sayput("Couldn't find tag for \"%s\"",pattern); return false; } /* failed */ to_begin_line(); return true; } /**************************************************************************** * tags_1 * Effect: * Loads the 'tags' file, searches it for the tag; if the tag is * found, go open the file in the current window and use the pattern * to locate the function. If not, complain. ****************************************************************************/ command tags_1() on reg_tab[ALT('.')] { int oldpoint = point; int start; char search_string[80]; char search_word[80]; char search_msg[120]; char * oldbuf = bufname; char fname[80]; int filestart; #define pattern search_msg int pattern_start; if(!parse_string(1, word_pattern, search_word)) strcpy(search_word,""); strcpy(search_msg,"Tag ["); strcat(search_msg,search_word); strcat(search_msg,"] :"); point = oldpoint; get_string(&search_string[1],search_msg); check_abort(); iter = 0; if(search_string[1] == '\0') strcpy(&search_string[1],search_word); /* Now see if we have a tag at all */ if(search_string[1] == '\0') return; /* Now make up an re_search string of the form ^word to find the search string in the tags file */ search_string[0] = '^'; strcat(search_string,"\t"); if(!load_tags()) return; /* couldn't find tags file */ /* If we loaded tags sucessfully, we are now in the tags buffer */ point = 0; if(!re_search(FORWARD,search_string)) { /* no such tag */ bufname = oldbuf; point = oldpoint; error("No tag \"%s\" found",search_string); } /* no such tag */ /* If we get here, the search was successful */ /* We have searched for a line of the form C_entries\tjunk.c\t/^C_entries ()$/ ^we are here */ filestart = point; if(!search(FORWARD,"\t")) { /* bad format */ bufname = oldbuf; point = oldpoint; error("internal error: bad format in tags file, no tab after filename"); } /* bad format */ /* We are now after the filename C_entries\tjunk.c\t/^C_entries ()$/ ^we are here */ /* Grab the file name */ grab(filestart,point - 1, fname); absolute(fname); /* make it absolute */ search(FORWARD,"/"); pattern_start = point; to_end_line(); search(BACKWARD,"/"); grab(pattern_start,point, pattern); point = oldpoint; /* restore point */ if(!locate_pattern(fname,pattern)) { /* no match */ bufname = oldbuf; point = oldpoint; error("Couldn't find \"%s\" in \"%s\"",pattern, fname); } /* no match */ /* bufname is now the buffer in which the tag was found */ to_buffer(bufname); } /* End of TAGS1.E */ #endif /* The following code is the code for Epsilon load_library. Extract it and use it with Epsilon*/ #if 0 /**************************************************************************** * load_library (jmn) * Inputs: * char * name: Library file name * Result: boolean * true if library loaded sucessfully * false if it failed for some reason * Effect: * Loads the bytes from the library file. Uses the library path * as first choice. * * Library path is based upon the variable ELIB ****************************************************************************/ boolean load_library(name) char * name; { char filename[80]; char * elib; elib = getenv("ELIB"); #if debug_load say("ELIB=%s",elib); #endif if(elib != NULL && strlen(elib) > 0) { /* try library path */ #if debug_load char * oldbuf = bufname; zap("debug"); to_buffer("debug"); bprintf("elib = %s.",elib); to_buffer(oldbuf); #endif strcpy(filename,elib); strcat(filename,name); strcat(filename,".b"); if(!check_file(filename, (struct file_info *) 0)) { /* file exists */ load_commands(filename); /* may abort to top-level */ return true; /* all done */ } /* file exists */ #if debug_load else { /* failed */ to_buffer("debug"); bprintf("Cannot find %s.",filename); to_buffer(oldbuf); } /* failed */ #endif } /* try library path */ /* The file does not exist on the elib path, or there is no elib path */ /* Try the default search path */ elib = lookpath(name); if(elib != NULL) { /* found on path */ load_commands(elib); } /* found on path */ else { /* use default */ load_commands(name); /* default path; may abort to top-level */ } /* use default */ return true; } /* End of load_library */ #endif /* This file is the 'makefile' which can be used to build 'tags'. Be sure to read it over carefully since it is tuned to my development environment. I use UniPress 'psmake'. The file 'go.bat' is included later IMPORTANT: Replace all instances of "%#" with "#" because the presence of the # in column 1 produces a very sick Lattice C compiler (it runs out of memory with 594K available). */ #if 0 %#***************************************************************************** %# makefile for TAGS %#***************************************************************************** %# Change Log %# Date | Change %#-----------+---------------------------------------------------------------- %# 4-Jul-86 | Created %#***************************************************************************** %# Standard prolog %# This declaration defines the location of the C compiler COMPILER = c:\lc\ %# This declaration defines the device on which the intermediate files %# (.Q files) are written. If a RAMdisk (e.g., E:) is selected, the %# compilation will run much faster VDISK=e: %# This declaration defines the device on which the output file is written. LDISK=d: %# The source extensions SRCEXT = .c .asm %############################################################################# %# Compilation rules. %############################################################################# %# Rules for compiling .c source files %# Note the use of the virtual disk 'm' for the memory model %# The association must be made in the 'go.bat' file .c.obj : del $*.obj $(COMPILER)lc1 -o$(VDISK) -ms -d -n -ic:\lc\ -cstu -im: $< $(COMPILER)lc2 -o$(LDISK) $(VDISK)$* if exist $*.obj goto $* echo * * * Compilation of '$*' failed >> nolink :$* .c.tag : tags -m -u $< .asm.obj : del $*.obj masm $*,$*,$*; if exist $*.obj goto $* echo * * * Assembly of $* failed >> nolink :$* %############################################################################## %# Object file specifications %############################################################################## TAGS =m:c.obj tags.obj %# The name of the linker files T=tags.lnk %############################################################################## %# The files %############################################################################## %# %# Note the use of the dummy name (xxxxx) which never exists, but captures %# the dependency of the two result files, tags and tags.exe. xxxxx: tags. tags.exe tags.: tags.tag TAGS.EXE: $(TAGS) $T makefile if not exist nolink goto oklink echo  ***** Compilation errors exist ***** type nolink goto nolink :oklink version version.c version.ver link @$(T),tags,tags,m:ctools+m:ctools2+m:lcm+m:lc/map/line/segments:256 :nolink %# The linker file. Note that it depends on 'makefile' so that it is %# always updated. If you add more OBFILESn, all but the last must %# include the + symbol at the end of line $T : makefile echo $(TAGS) > $T tags.obj : l:stdio.h l:ctype.h l:string.h l:dos.h tags.tag : tags.c #endif