home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Professional / OS2PRO194.ISO / os2 / editor / elvis / sources / ctags.c < prev    next >
C/C++ Source or Header  |  1994-01-30  |  18KB  |  820 lines

  1. /* ctags.c */
  2.  
  3. /* This is a reimplementation of the ctags(1) program.  It supports ANSI C,
  4.  * and has heaps o' flags.  It is meant to be distributed with elvis.
  5.  */
  6.  
  7. #include <stdio.h>
  8. #include "config.h"
  9. #ifndef FALSE
  10. # define FALSE    0
  11. # define TRUE    1
  12. #endif
  13. #ifndef TAGS
  14. # define TAGS    "tags"
  15. #endif
  16. #ifndef REFS
  17. # define REFS    "refs"
  18. #endif
  19. #ifndef BLKSIZE
  20. # define BLKSIZE 1024
  21. #endif
  22.  
  23. #include "ctype.c" /* yes, that really is the .c file, not the .h one. */
  24.  
  25. /* -------------------------------------------------------------------------- */
  26. /* Some global variables */
  27.  
  28. /* The following boolean variables are set according to command line flags */
  29. int    incl_static;    /* -s  include static tags */
  30. int    incl_types;    /* -t  include typedefs and structs */
  31. int    incl_vars;    /* -v  include variables */
  32. int    make_refs;    /* -r  generate a "refs" file */
  33. int    append_files;    /* -a  append to "tags" [and "refs"] files */
  34.  
  35. /* The following are used for outputting to the "tags" and "refs" files */
  36. FILE    *tags;        /* used for writing to the "tags" file */
  37. FILE    *refs;        /* used for writing to the "refs" file */
  38.  
  39. /* -------------------------------------------------------------------------- */
  40. /* These are used for reading a source file.  It keeps track of line numbers */
  41. char    *file_name;    /* name of the current file */
  42. FILE    *file_fp;    /* stream used for reading the file */
  43. long    file_lnum;    /* line number in the current file */
  44. long    file_seek;    /* fseek() offset to the start of current line */
  45. int    file_afternl;    /* boolean: was previous character a newline? */
  46. int    file_prevch;    /* a single character that was ungotten */
  47. int    file_header;    /* boolean: is the current file a header file? */
  48.  
  49. /* This function opens a file, and resets the line counter.  If it fails, it
  50.  * it will display an error message and leave the file_fp set to NULL.
  51.  */
  52. void file_open(name)
  53.     char    *name;    /* name of file to be opened */
  54. {
  55.     /* if another file was already open, then close it */
  56.     if (file_fp)
  57.     {
  58.         fclose(file_fp);
  59.     }
  60.  
  61.     /* try to open the file for reading.  The file must be opened in
  62.      * "binary" mode because otherwise fseek() would misbehave under DOS.
  63.      */
  64. #if MSDOS || TOS
  65.     file_fp = fopen(name, "rb");
  66. #else
  67.     file_fp = fopen(name, "r");
  68. #endif
  69.     if (!file_fp)
  70.     {
  71.         perror(name);
  72.     }
  73.  
  74.     /* reset the name & line number */
  75.     file_name = name;
  76.     file_lnum = 0L;
  77.     file_seek = 0L;
  78.     file_afternl = TRUE;
  79.  
  80.     /* determine whether this is a header file */
  81.     file_header = FALSE;
  82.     name += strlen(name) - 2;
  83.     if (name >= file_name && name[0] == '.' && (name[1] == 'h' || name[1] == 'H'))
  84.     {
  85.         file_header = TRUE;
  86.     }
  87. }
  88.  
  89. /* This function reads a single character from the stream.  If the *previous*
  90.  * character was a newline, then it also increments file_lnum and sets
  91.  * file_offset.
  92.  */
  93. int file_getc()
  94. {
  95.     int    ch;
  96.  
  97.     /* if there is an ungotten character, then return it.  Don't do any
  98.      * other processing on it, though, because we already did that the
  99.      * first time it was read.
  100.      */
  101.     if (file_prevch)
  102.     {
  103.         ch = file_prevch;
  104.         file_prevch = 0;
  105.         return ch;
  106.     }
  107.  
  108.     /* if previous character was a newline, then we're starting a line */
  109.     if (file_afternl)
  110.     {
  111.         file_afternl = FALSE;
  112.         file_seek = ftell(file_fp);
  113.         file_lnum++;
  114.     }
  115.  
  116.     /* Get a character.  If no file is open, then return EOF */
  117.     ch = (file_fp ? getc(file_fp) : EOF);
  118.  
  119.     /* if it is a newline, then remember that fact */
  120.     if (ch == '\n')
  121.     {
  122.         file_afternl = TRUE;
  123.     }
  124.  
  125.     /* return the character */
  126.     return ch;
  127. }
  128.  
  129. /* This function ungets a character from the current source file */
  130. void file_ungetc(ch)
  131.     int    ch;    /* character to be ungotten */
  132. {
  133.     file_prevch = ch;
  134. }
  135.  
  136. /* This function copies the current line out some other fp.  It has no effect
  137.  * on the file_getc() function.  During copying, any '\' characters are doubled
  138.  * and a leading '^' or trailing '$' is also quoted.  The newline character is
  139.  * not copied.
  140.  *
  141.  * This is meant to be used when generating a tag line.
  142.  */
  143. void file_copyline(seek, fp)
  144.     long    seek;    /* where the lines starts in the source file */
  145.     FILE    *fp;    /* the output stream to copy it to */
  146. {
  147.     long    oldseek;/* where the file's pointer was before we messed it up */
  148.     char    ch;    /* a single character from the file */
  149.     char    next;    /* the next character from this file */
  150.  
  151.     /* go to the start of the line */
  152.     oldseek = ftell(file_fp);
  153.     fseek(file_fp, seek, 0);
  154.  
  155.     /* if first character is '^', then emit \^ */
  156.     ch = getc(file_fp);
  157.     if (ch == '^')
  158.     {
  159.         putc('\\', fp);
  160.         putc('^', fp);
  161.         ch = getc(file_fp);
  162.     }
  163.  
  164.     /* write everything up to, but not including, the newline */
  165.     while (ch != '\n')
  166.     {
  167.         /* preread the next character from this file */
  168.         next = getc(file_fp);
  169.  
  170.         /* if character is '\', or a terminal '$', then quote it */
  171.         if (ch == '\\' || (ch == '$' && next == '\n'))
  172.         {
  173.             putc('\\', fp);
  174.         }
  175.         putc(ch, fp);
  176.  
  177.         /* next character... */
  178.         ch = next;
  179.     }
  180.  
  181.     /* seek back to the old position */
  182.     fseek(file_fp, oldseek, 0);
  183. }
  184.  
  185. /* -------------------------------------------------------------------------- */
  186. /* This section handles preprocessor directives.  It strips out all of the
  187.  * directives, and may emit a tag for #define directives.
  188.  */
  189.  
  190. int    cpp_afternl;    /* boolean: look for '#' character? */
  191. int    cpp_prevch;    /* an ungotten character, if any */
  192. int    cpp_refsok;    /* boolean: can we echo characters out to "refs"? */
  193.  
  194. /* This function opens the file & resets variables */
  195. void cpp_open(name)
  196.     char    *name;    /* name of source file to be opened */
  197. {
  198.     /* use the lower-level file_open function to open the file */
  199.     file_open(name);
  200.  
  201.     /* reset variables */
  202.     cpp_afternl = TRUE;
  203.     cpp_refsok = TRUE;
  204. }
  205.  
  206. /* This function copies a character from the source file to the "refs" file */
  207. void cpp_echo(ch)
  208.     int    ch; /* the character to copy */
  209. {
  210.     static    wasnl;
  211.  
  212.     /* echo non-EOF chars, unless not making "ref", or echo turned off */
  213.     if (ch != EOF && make_refs && cpp_refsok && !file_header)
  214.     {
  215.         /* try to avoid blank lines */
  216.         if (ch == '\n')
  217.         {
  218.             if (wasnl)
  219.             {
  220.                 return;
  221.             }
  222.             wasnl = TRUE;
  223.         }
  224.         else
  225.         {
  226.             wasnl = FALSE;
  227.         }
  228.  
  229.         /* add the character */
  230.         putc(ch, refs);
  231.     }
  232. }
  233.  
  234. /* This function returns the next character which isn't part of a directive */
  235. int cpp_getc()
  236. {
  237.     static
  238.     int    ch;    /* the next input character */
  239.     char    *scan;
  240.  
  241.     /* if we have an ungotten character, then return it */
  242.     if (cpp_prevch)
  243.     {
  244.         ch = cpp_prevch;
  245.         cpp_prevch = 0;
  246.         return ch;
  247.     }
  248.  
  249.     /* Get a character from the file.  Return it if not special '#' */
  250.     ch = file_getc();
  251.     if (ch == '\n')
  252.     {
  253.         cpp_afternl = TRUE;
  254.         cpp_echo(ch);
  255.         return ch;
  256.     }
  257.     else if (ch != '#' || !cpp_afternl)
  258.     {
  259.         /* normal character.  Any non-whitespace should turn off afternl */
  260.         if (ch != ' ' && ch != '\t')
  261.         {
  262.             cpp_afternl = FALSE;
  263.         }
  264.         cpp_echo(ch);
  265.         return ch;
  266.     }
  267.  
  268.     /* Yikes!  We found a directive */
  269.  
  270.     /* see whether this is a #define line */
  271.     scan = " define ";
  272.     while (*scan)
  273.     {
  274.         if (*scan == ' ')
  275.         {
  276.             /* space character matches any whitespace */
  277.             do
  278.             {
  279.                 ch = file_getc();
  280.             } while (ch == ' ' || ch == '\t');
  281.             file_ungetc(ch);
  282.         }
  283.         else
  284.         {
  285.             /* other characters should match exactly */
  286.             ch = file_getc();
  287.             if (ch != *scan)
  288.             {
  289.                 file_ungetc(ch);
  290.                 break;
  291.             }
  292.         }
  293.         scan++;
  294.     }
  295.  
  296.     /* is this a #define line?  and should we generate a tag for it? */
  297.     if (!*scan && (file_header || incl_static))
  298.     {
  299.         /* if not a header, then this will be a static tag */
  300.         if (!file_header)
  301.         {
  302.             fputs(file_name, tags);
  303.             putc(':', tags);
  304.         }
  305.  
  306.         /* output the tag name */
  307.         for (ch = file_getc(); isalnum(ch) || ch == '_'; ch = file_getc())
  308.         {
  309.             putc(ch, tags);
  310.         }
  311.  
  312.         /* output a tab, the filename, another tab, and the line number */
  313.         fprintf(tags, "\t%s\t%ld\n", file_name, file_lnum);
  314.     }
  315.  
  316.     /* skip to the end of the directive -- a newline that isn't preceded
  317.      * by a '\' character.
  318.      */
  319.     while (ch != EOF && ch != '\n')
  320.     {
  321.         if (ch == '\\')
  322.         {
  323.             ch = file_getc();
  324.         }
  325.         ch = file_getc();
  326.     }
  327.  
  328.     /* return the newline that we found at the end of the directive */
  329.     cpp_echo(ch);
  330.     return ch;
  331. }
  332.  
  333. /* This puts a character back into the input queue for the source file */
  334. cpp_ungetc(ch)
  335.     int    ch;    /* a character to be ungotten */
  336. {
  337.     cpp_prevch = ch;
  338. }
  339.  
  340.  
  341. /* -------------------------------------------------------------------------- */
  342. /* This is the lexical analyser.  It gets characters from the preprocessor,
  343.  * and gives tokens to the parser.  Some special codes are...
  344.  *   (deleted)  /*...* /    (comments)
  345.  *   (deleted)    //...\n    (comments)
  346.  *   (deleted)    (*    (parens used in complex declaration)
  347.  *   (deleted)    [...]    (array subscript, when ... contains no ])
  348.  *   (deleted)    struct    (intro to structure declaration)
  349.  *   BODY    {...}    ('{' can occur anywhere, '}' only at BOW if ... has '{')
  350.  *   ARGS    (...{    (args of function, not extern or forward)
  351.  *   ARGS    (...);    (args of an extern/forward function declaration)
  352.  *   COMMA    ,    (separate declarations that have same scope)
  353.  *   SEMICOLON    ;    (separate declarations that have different scope)
  354.  *   SEMICOLON  =...;    (initializer)
  355.  *   TYPEDEF    typedef    (the "typedef" keyword)
  356.  *   STATIC    static    (the "static" keyword)
  357.  *   STATIC    private    (the "static" keyword)
  358.  *   STATIC    PRIVATE    (the "static" keyword)
  359.  *   NAME    [a-z]+    (really any valid name that isn't reserved word)
  360.  */
  361.  
  362. /* #define EOF -1 */
  363. #define DELETED      0
  364. #define BODY      1
  365. #define ARGS      2
  366. #define COMMA      3
  367. #define SEMICOLON 4
  368. #define TYPEDEF   5
  369. #define STATIC      6
  370. #define EXTERN      7
  371. #define NAME      8
  372.  
  373. char    lex_name[BLKSIZE];    /* the name of a "NAME" token */
  374. long    lex_seek;        /* start of line that contains lex_name */
  375.  
  376. lex_gettoken()
  377. {
  378.     int    ch;        /* a character from the preprocessor */
  379.     int    next;        /* the next character */
  380.     int    token;        /* the token that we'll return */
  381.     int    i;
  382.  
  383.     /* loop until we get a token that isn't "DELETED" */
  384.     do
  385.     {
  386.         /* get the next character */
  387.         ch = cpp_getc();
  388.  
  389.         /* process the character */
  390.         switch (ch)
  391.         {
  392.           case ',':
  393.             token = COMMA;
  394.             break;
  395.  
  396.           case ';':
  397.             token = SEMICOLON;
  398.             break;
  399.  
  400.           case '/':
  401.             /* get the next character */
  402.             ch = cpp_getc();
  403.             switch (ch)
  404.             {
  405.               case '*':    /* start of C comment */
  406.                 ch = cpp_getc();
  407.                 next = cpp_getc();
  408.                 while (next != EOF && (ch != '*' || next != '/'))
  409.                 {
  410.                     ch = next;
  411.                     next = cpp_getc();
  412.                 }
  413.                 break;
  414.  
  415.               case '/':    /* start of a C++ comment */
  416.                 do
  417.                 {
  418.                     ch = cpp_getc();
  419.                 } while (ch != '\n' && ch != EOF);
  420.                 break;
  421.  
  422.               default:    /* some other slash */
  423.                 cpp_ungetc(ch);
  424.             }
  425.             token = DELETED;
  426.             break;
  427.  
  428.           case '(':
  429.             ch = cpp_getc();
  430.             if (ch == '*')
  431.             {
  432.                 token = DELETED;
  433.             }
  434.             else
  435.             {
  436.                 next = cpp_getc();
  437.                 while (ch != '{' && ch != EOF && (ch != ')' || next != ';'))/*}*/
  438.                 {
  439.                     ch = next;
  440.                     next = cpp_getc();
  441.                 }
  442.                 if (ch == '{')/*}*/
  443.                 {
  444.                     cpp_ungetc(ch);
  445.                 }
  446.                 else if (next == ';')
  447.                 {
  448.                     cpp_ungetc(next);
  449.                 }
  450.                 token = ARGS;
  451.             }
  452.             break;
  453.  
  454.           case '{':/*}*/
  455.             /* don't send the next characters to "refs" */
  456.             cpp_refsok = FALSE;
  457.  
  458.             /* skip ahead to closing '}', or to embedded '{' */
  459.             do
  460.             {
  461.                 ch = cpp_getc();
  462.             } while (ch != '{' && ch != '}' && ch != EOF);
  463.  
  464.             /* if has embedded '{', then skip to '}' in column 1 */
  465.             if (ch == '{') /*}*/
  466.             {
  467.                 ch = cpp_getc();
  468.                 next = cpp_getc();
  469.                 while (ch != EOF && (ch != '\n' || next != '}'))/*{*/
  470.                 {
  471.                     ch = next;
  472.                     next = cpp_getc();
  473.                 }
  474.             }
  475.  
  476.             /* resume "refs" processing */
  477.             cpp_refsok = TRUE;
  478.             cpp_echo('}');
  479.  
  480.             token = BODY;
  481.             break;
  482.  
  483.           case '[':
  484.             /* skip to matching ']' */
  485.             do
  486.             {
  487.                 ch = cpp_getc();
  488.             } while (ch != ']' && ch != EOF);
  489.             token = DELETED;
  490.             break;
  491.  
  492.           case '=':
  493.               /* skip to next ';' */
  494.             do
  495.             {
  496.                 ch = cpp_getc();
  497.  
  498.                 /* leave array initializers out of "refs" */
  499.                 if (ch == '{')
  500.                 {
  501.                     cpp_refsok = FALSE;
  502.                 }
  503.             } while (ch != ';' && ch != EOF);
  504.  
  505.             /* resume echoing to "refs" */
  506.             if (!cpp_refsok)
  507.             {
  508.                 cpp_refsok = TRUE;
  509.                 cpp_echo('}');
  510.                 cpp_echo(';');
  511.             }
  512.             token = SEMICOLON;
  513.             break;
  514.  
  515.           case EOF:
  516.             token = EOF;
  517.             break;
  518.  
  519.           default:
  520.             /* is this the start of a name/keyword? */
  521.             if (isalpha(ch) || ch == '_')
  522.             {
  523.                 /* collect the whole word */
  524.                 lex_name[0] = ch;
  525.                 for (i = 1, ch = cpp_getc();
  526.                      i < BLKSIZE - 1 && (isalnum(ch) || ch == '_');
  527.                      i++, ch = cpp_getc())
  528.                 {
  529.                     lex_name[i] = ch;
  530.                 }
  531.                 lex_name[i] = '\0';
  532.                 cpp_ungetc(ch);
  533.  
  534.                 /* is it a reserved word? */
  535.                 if (!strcmp(lex_name, "typedef"))
  536.                 {
  537.                     token = TYPEDEF;
  538.                     lex_seek = -1L;
  539.                 }
  540.                 else if (!strcmp(lex_name, "static")
  541.                       || !strcmp(lex_name, "private")
  542.                       || !strcmp(lex_name, "PRIVATE"))
  543.                 {
  544.                     token = STATIC;
  545.                     lex_seek = -1L;
  546.                 }
  547.                 else if (!strcmp(lex_name, "extern")
  548.                       || !strcmp(lex_name, "EXTERN")
  549.                       || !strcmp(lex_name, "FORWARD"))
  550.                 {
  551.                     token = EXTERN;
  552.                     lex_seek = -1L;
  553.                 }
  554.                 else
  555.                 {
  556.                     token = NAME;
  557.                     lex_seek = file_seek;
  558.                 }
  559.             }
  560.             else /* not part of a name/keyword */
  561.             {
  562.                 token = DELETED;
  563.             }
  564.  
  565.         } /* end switch(ch) */
  566.  
  567.     } while (token == DELETED);
  568.  
  569.     return token;
  570. }
  571.  
  572. /* -------------------------------------------------------------------------- */
  573. /* This is the parser.  It locates tag candidates, and then decides whether to
  574.  * generate a tag for them.
  575.  */
  576.  
  577. /* This function generates a tag for the object in lex_name, whose tag line is
  578.  * located at a given seek offset.
  579.  */
  580. void maketag(scope, seek)
  581.     int    scope;    /* 0 if global, or STATIC if static */
  582.     long    seek;    /* the seek offset of the line */
  583. {
  584.     /* output the tagname and filename fields */
  585.     if (scope == EXTERN)
  586.     {
  587.         /* whoa!  we should *never* output a tag for "extern" decl */
  588.         return;
  589.     }
  590.     else if (scope == STATIC)
  591.     {
  592.         fprintf(tags, "%s:%s\t%s\t", file_name, lex_name, file_name);
  593.     }
  594.     else
  595.     {
  596.         fprintf(tags, "%s\t%s\t", lex_name, file_name);
  597.     }
  598.  
  599.     /* output the target line */
  600.     putc('/', tags);
  601.     putc('^', tags);
  602.     file_copyline(seek, tags);
  603.     putc('$', tags);
  604.     putc('/', tags);
  605.     putc('\n', tags);
  606. }
  607.  
  608.  
  609. /* This function parses a source file, adding any tags that it finds */
  610. void ctags(name)
  611.     char    *name;    /* the name of a source file to be checked */
  612. {
  613.     int    prev;    /* the previous token from the source file */
  614.     int    token;    /* the current token from the source file */
  615.     int    scope;    /* normally 0, but could be a TYPEDEF or STATIC token */
  616.     int    gotname;/* boolean: does lex_name contain a tag candidate? */
  617.     long    tagseek;/* start of line that contains lex_name */
  618.  
  619.     /* open the file */
  620.     cpp_open(name);
  621.  
  622.     /* reset */
  623.     scope = 0;
  624.     gotname = FALSE;
  625.     token = SEMICOLON;
  626.  
  627.     /* parse until the end of the file */
  628.     while (prev = token, (token = lex_gettoken()) != EOF)
  629.     {
  630.         /* scope keyword? */
  631.         if (token == TYPEDEF || token == STATIC || token == EXTERN)
  632.         {
  633.             scope = token;
  634.             gotname = FALSE;
  635.             continue;
  636.         }
  637.  
  638.         /* name of a possible tag candidate? */
  639.         if (token == NAME)
  640.         {
  641.             tagseek = file_seek;
  642.             gotname = TRUE;
  643.             continue;
  644.         }
  645.  
  646.         /* if NAME BODY, without ARGS, then NAME is a struct tag */
  647.         if (gotname && token == BODY && prev != ARGS)
  648.         {
  649.             gotname = FALSE;
  650.             
  651.             /* ignore if in typedef -- better name is coming soon */
  652.             if (scope == TYPEDEF)
  653.             {
  654.                 continue;
  655.             }
  656.  
  657.             /* generate a tag, if -t and maybe -s */
  658.             if (incl_types && (file_header || incl_static))
  659.             {
  660.                 maketag(file_header ? 0 : STATIC, tagseek);
  661.             }
  662.         }
  663.  
  664.         /* If NAME ARGS BODY, then NAME is a function */
  665.         if (gotname && prev == ARGS && token == BODY)
  666.         {
  667.             gotname = FALSE;
  668.             
  669.             /* generate a tag, maybe checking -s */
  670.             if (scope != STATIC || incl_static)
  671.             {
  672.                 maketag(scope, tagseek);
  673.             }
  674.         }
  675.  
  676.         /* If NAME SEMICOLON or NAME COMMA, then NAME is var/typedef */
  677.         if (gotname && (token == SEMICOLON || token == COMMA))
  678.         {
  679.             gotname = FALSE;
  680.  
  681.             /* generate a tag, if -v/-t and maybe -s */
  682.             if (scope == TYPEDEF && incl_types && (file_header || incl_static)
  683.              || scope == STATIC && incl_vars && incl_static
  684.              || incl_vars)
  685.             {
  686.                 /* a TYPEDEF outside of a header is STATIC */
  687.                 if (scope == TYPEDEF && !file_header)
  688.                 {
  689.                     maketag(STATIC, tagseek);
  690.                 }
  691.                 else /* use whatever scope was declared */
  692.                 {
  693.                     maketag(scope, tagseek);
  694.                 }
  695.             }
  696.         }
  697.  
  698.         /* reset after a semicolon or ARGS BODY pair */
  699.         if (token == SEMICOLON || (prev == ARGS && token == BODY))
  700.         {
  701.             scope = 0;
  702.             gotname = FALSE;
  703.         }
  704.     }
  705.  
  706.     /* The source file will be automatically closed */
  707. }
  708.  
  709. /* -------------------------------------------------------------------------- */
  710.  
  711. void usage()
  712. {
  713.     fprintf(stderr, "usage: ctags [flags] filenames...\n");
  714.     fprintf(stderr, "\t-s  include static functions\n");
  715.     fprintf(stderr, "\t-t  include typedefs\n");
  716.     fprintf(stderr, "\t-v  include variable declarations\n");
  717.     fprintf(stderr, "\t-r  generate a \"refs\" file, too\n");
  718.     fprintf(stderr, "\t-a  append to \"tags\", instead of overwriting\n");
  719.     exit(2);
  720. }
  721.  
  722.  
  723.  
  724. #if AMIGA
  725. # include "amiwild.c"
  726. #endif
  727.  
  728. #if VMS
  729. # include "vmswild.c"
  730. #endif
  731.  
  732. main(argc, argv)
  733.     int    argc;
  734.     char    **argv;
  735. {
  736.     int    i, j;
  737.  
  738. #if MSDOS || TOS
  739.     char    **wildexpand();
  740.     argv = wildexpand(&argc, argv);
  741. #endif
  742.  
  743.     /* build the tables used by the ctype macros */
  744.     _ct_init("");
  745.  
  746.     /* parse the option flags */
  747.     for (i = 1; i < argc && argv[i][0] == '-'; i++)
  748.     {
  749.         for (j = 1; argv[i][j]; j++)
  750.         {
  751.             switch (argv[i][j])
  752.             {
  753.               case 's':    incl_static = TRUE;    break;
  754.               case 't':    incl_types = TRUE;    break;
  755.               case 'v':    incl_vars = TRUE;    break;
  756.               case 'r':    make_refs = TRUE;    break;
  757.               case 'a':    append_files = TRUE;    break;
  758.               default:    usage();
  759.             }
  760.         }
  761.     }
  762.  
  763.     /* There should always be at least one source file named in args */
  764.     if (i == argc)
  765.     {
  766.         usage();
  767.     }
  768.  
  769.     /* open the "tags" and maybe "refs" files */
  770.     tags = fopen(TAGS, append_files ? "a" : "w");
  771.     if (!tags)
  772.     {
  773.         perror(TAGS);
  774.         exit(3);
  775.     }
  776.     if (make_refs)
  777.     {
  778.         refs = fopen(REFS, append_files ? "a" : "w");
  779.         if (!refs)
  780.         {
  781.             perror(REFS);
  782.             exit(4);
  783.         }
  784.     }
  785.  
  786.     /* parse each source file */
  787.     for (; i < argc; i++)
  788.     {
  789.         ctags(argv[i]);
  790.     }
  791.  
  792.     /* close "tags" and maybe "refs" */
  793.     fclose(tags);
  794.     if (make_refs)
  795.     {
  796.         fclose(refs);
  797.     }
  798.  
  799. #ifdef SORT
  800.         /* This is a hack which will sort the tags list.   It should
  801.          * on UNIX and OS-9.  You may have trouble with csh.   Note
  802.          * that the tags list only has to be sorted if you intend to
  803.          * use it with the real vi;  elvis permits unsorted tags.
  804.          */
  805. # if OSK
  806.         system("qsort tags >-_tags; -nx; del tags; rename _tags tags");
  807. # else    
  808.         system("sort tags >_tags$$; mv _tags$$ tags");
  809. # endif
  810. #endif
  811.  
  812.     exit(0);
  813.     /*NOTREACHED*/
  814. }
  815.  
  816. #if MSDOS || TOS
  817. # define WILDCARD_NO_MAIN
  818. # include "wildcard.c"
  819. #endif
  820.