Revert previous change.
[bpt/emacs.git] / lib-src / etags.c
index 9503895..3632e23 100644 (file)
@@ -33,7 +33,7 @@ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
  *     Francesco Potortì <pot@gnu.org> has maintained it since 1993.
  */
 
-char pot_etags_version[] = "@(#) pot revision number is 14.35";
+char pot_etags_version[] = "@(#) pot revision number is 15.2";
 
 #define        TRUE    1
 #define        FALSE   0
@@ -299,13 +299,11 @@ static void print_language_names __P((void));
 static void print_version __P((void));
 static void print_help __P((void));
 int main __P((int, char **));
-static int number_len __P((long));
 
 static compressor *get_compressor_from_suffix __P((char *, char **));
 static language *get_language_from_langname __P((const char *));
 static language *get_language_from_interpreter __P((char *));
 static language *get_language_from_filename __P((char *));
-static int total_size_of_entries __P((node *));
 static long readline __P((linebuffer *, FILE *));
 static long readline_internal __P((linebuffer *, FILE *));
 static bool nocase_tail __P((char *));
@@ -345,33 +343,37 @@ static char *absolute_dirname __P((char *, char *));
 static bool filename_is_absolute __P((char *f));
 static void canonicalize_filename __P((char *));
 static void linebuffer_setlen __P((linebuffer *, int));
-PTR xmalloc __P((unsigned int));
-PTR xrealloc __P((char *, unsigned int));
+static PTR xmalloc __P((unsigned int));
+static PTR xrealloc __P((char *, unsigned int));
 
 \f
-char searchar = '/';           /* use /.../ searches */
+static char searchar = '/';    /* use /.../ searches */
 
-char *tagfile;                 /* output file */
-char *progname;                        /* name this program was invoked with */
-char *cwd;                     /* current working directory */
-char *tagfiledir;              /* directory of tagfile */
-FILE *tagf;                    /* ioptr for tags file */
+static char *tagfile;          /* output file */
+static char *progname;         /* name this program was invoked with */
+static char *cwd;              /* current working directory */
+static char *tagfiledir;       /* directory of tagfile */
+static FILE *tagf;             /* ioptr for tags file */
 
-char *curfile;                 /* current input file name */
-language *curlang;             /* current language */
+static char *curfile;          /* current input uncompressed file name */
+static char *curfiledir;       /* absolute dir of curfile */
+static char *curtagfname;      /* current file name to write in tagfile */
+static language *curlang;      /* current language */
 
-int lineno;                    /* line number of current line */
-long charno;                   /* current character number */
-long linecharno;               /* charno of start of current line */
-char *dbp;                     /* pointer to start of current tag */
+static int lineno;             /* line number of current line */
+static long charno;            /* current character number */
+static long linecharno;                /* charno of start of current line */
+static char *dbp;              /* pointer to start of current tag */
+static bool nocharno;          /* only use line number when making tag  */
+static const int invalidcharno = -1;
 
-node *head;                    /* the head of the binary tree of tags */
+static node *head;             /* the head of the binary tree of tags */
 
-linebuffer lb;                 /* the current line */
+static linebuffer lb;          /* the current line */
 
 /* boolean "functions" (see init)      */
-bool _wht[CHARS], _nin[CHARS], _itk[CHARS], _btk[CHARS], _etk[CHARS];
-char
+static bool _wht[CHARS], _nin[CHARS], _itk[CHARS], _btk[CHARS], _etk[CHARS];
+static char
   /* white chars */
   *white = " \f\t\n\r\v",
   /* not in a name */
@@ -383,58 +385,60 @@ char
   /* valid in-token chars */
   *midtk = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz$0123456789";
 
-bool append_to_tagfile;                /* -a: append to tags */
+static bool append_to_tagfile; /* -a: append to tags */
 /* The following four default to TRUE for etags, but to FALSE for ctags.  */
-bool typedefs;                 /* -t: create tags for C and Ada typedefs */
-bool typedefs_or_cplusplus;    /* -T: create tags for C typedefs, level */
+static bool typedefs;          /* -t: create tags for C and Ada typedefs */
+static bool typedefs_or_cplusplus; /* -T: create tags for C typedefs, level */
                                /* 0 struct/enum/union decls, and C++ */
                                /* member functions. */
-bool constantypedefs;          /* -d: create tags for C #define, enum */
+static bool constantypedefs;   /* -d: create tags for C #define, enum */
                                /* constants and variables. */
                                /* -D: opposite of -d.  Default under ctags. */
-bool declarations;             /* --declarations: tag them and extern in C&Co*/
-bool globals;                  /* create tags for global variables */
-bool members;                  /* create tags for C member variables */
-bool update;                   /* -u: update tags */
-bool vgrind_style;             /* -v: create vgrind style index output */
-bool no_warnings;              /* -w: suppress warnings */
-bool cxref_style;              /* -x: create cxref style output */
-bool cplusplus;                        /* .[hc] means C++, not C */
-bool noindentypedefs;          /* -I: ignore indentation in C */
-bool packages_only;            /* --packages-only: in Ada, only tag packages*/
+static bool declarations;      /* --declarations: tag them and extern in C&Co*/
+static bool globals;           /* create tags for global variables */
+static bool no_line_directive; /* ignore #line directives */
+static bool members;           /* create tags for C member variables */
+static bool update;            /* -u: update tags */
+static bool vgrind_style;      /* -v: create vgrind style index output */
+static bool no_warnings;       /* -w: suppress warnings */
+static bool cxref_style;       /* -x: create cxref style output */
+static bool cplusplus;         /* .[hc] means C++, not C */
+static bool noindentypedefs;   /* -I: ignore indentation in C */
+static bool packages_only;     /* --packages-only: in Ada, only tag packages*/
 
 #ifdef LONG_OPTIONS
-struct option longopts[] =
+static struct option longopts[] =
 {
-  { "packages-only",      no_argument,      &packages_only, TRUE  },
-  { "append",            no_argument,       NULL,           'a'   },
-  { "backward-search",   no_argument,       NULL,           'B'   },
-  { "c++",               no_argument,       NULL,           'C'   },
-  { "cxref",             no_argument,       NULL,           'x'   },
-  { "defines",           no_argument,       NULL,           'd'   },
-  { "declarations",      no_argument,       &declarations,  TRUE  },
-  { "no-defines",        no_argument,       NULL,           'D'   },
-  { "globals",           no_argument,       &globals,       TRUE  },
-  { "no-globals",        no_argument,       &globals,       FALSE },
-  { "help",              no_argument,       NULL,           'h'   },
-  { "help",              no_argument,       NULL,           'H'   },
-  { "ignore-indentation", no_argument,      NULL,           'I'   },
-  { "include",           required_argument, NULL,           'i'   },
-  { "language",           required_argument, NULL,                  'l'   },
-  { "members",           no_argument,       &members,       TRUE  },
-  { "no-members",        no_argument,       &members,       FALSE },
-  { "no-warn",           no_argument,       NULL,           'w'   },
-  { "output",            required_argument, NULL,           'o'   },
+  { "packages-only",      no_argument,      &packages_only,     TRUE  },
+  { "append",            no_argument,       NULL,               'a'   },
+  { "backward-search",   no_argument,       NULL,               'B'   },
+  { "c++",               no_argument,       NULL,               'C'   },
+  { "cxref",             no_argument,       NULL,               'x'   },
+  { "defines",           no_argument,       NULL,               'd'   },
+  { "declarations",      no_argument,       &declarations,      TRUE  },
+  { "no-defines",        no_argument,       NULL,               'D'   },
+  { "globals",           no_argument,       &globals,           TRUE  },
+  { "no-globals",        no_argument,       &globals,           FALSE },
+  { "no-line-directive",  no_argument,      &no_line_directive, TRUE  },
+  { "help",              no_argument,       NULL,               'h'   },
+  { "help",              no_argument,       NULL,               'H'   },
+  { "ignore-indentation", no_argument,      NULL,               'I'   },
+  { "include",           required_argument, NULL,               'i'   },
+  { "language",           required_argument, NULL,                      'l'   },
+  { "members",           no_argument,       &members,           TRUE  },
+  { "no-members",        no_argument,       &members,           FALSE },
+  { "no-warn",           no_argument,       NULL,               'w'   },
+  { "output",            required_argument, NULL,               'o'   },
 #ifdef ETAGS_REGEXPS
-  { "regex",             required_argument, NULL,           'r'   },
-  { "no-regex",                  no_argument,       NULL,           'R'   },
-  { "ignore-case-regex",  required_argument, NULL,          'c'   },
+  { "regex",             required_argument, NULL,               'r'   },
+  { "no-regex",                  no_argument,       NULL,               'R'   },
+  { "ignore-case-regex",  required_argument, NULL,              'c'   },
 #endif /* ETAGS_REGEXPS */
-  { "typedefs",                  no_argument,       NULL,           't'   },
-  { "typedefs-and-c++",          no_argument,       NULL,           'T'   },
-  { "update",            no_argument,       NULL,           'u'   },
-  { "version",           no_argument,       NULL,           'V'   },
-  { "vgrind",            no_argument,       NULL,           'v'   },
+  { "typedefs",                  no_argument,       NULL,               't'   },
+  { "typedefs-and-c++",          no_argument,       NULL,               'T'   },
+  { "update",            no_argument,       NULL,               'u'   },
+  { "version",           no_argument,       NULL,               'V'   },
+  { "vgrind",            no_argument,       NULL,               'v'   },
   { NULL }
 };
 #endif /* LONG_OPTIONS */
@@ -454,15 +458,15 @@ typedef struct pattern
 } pattern;
 
 /* List of all regexps. */
-pattern *p_head = NULL;
+static pattern *p_head = NULL;
 
 /* How many characters in the character set.  (From regex.c.)  */
 #define CHAR_SET_SIZE 256
 /* Translation table for case-insensitive matching. */
-char lc_trans[CHAR_SET_SIZE];
+static char lc_trans[CHAR_SET_SIZE];
 #endif /* ETAGS_REGEXPS */
 
-compressor compressors[] =
+static compressor compressors[] =
 {
   { "z", "gzip -d -c"},
   { "Z", "gzip -d -c"},
@@ -477,94 +481,96 @@ compressor compressors[] =
  */
 
 /* Non-NULL if language fixed. */
-language *forced_lang = NULL;
+static language *forced_lang = NULL;
 
 /* Ada code */
-char *Ada_suffixes [] =
+static char *Ada_suffixes [] =
   { "ads", "adb", "ada", NULL };
 
 /* Assembly code */
-char *Asm_suffixes [] = { "a", /* Unix assembler */
-                         "asm", /* Microcontroller assembly */
-                         "def", /* BSO/Tasking definition includes  */
-                         "inc", /* Microcontroller include files */
-                         "ins", /* Microcontroller include files */
-                         "s", "sa", /* Unix assembler */
-                         "S",   /* cpp-processed Unix assembler */
-                         "src", /* BSO/Tasking C compiler output */
-                         NULL
-                       };
+static char *Asm_suffixes [] =
+  { "a",       /* Unix assembler */
+    "asm", /* Microcontroller assembly */
+    "def", /* BSO/Tasking definition includes  */
+    "inc", /* Microcontroller include files */
+    "ins", /* Microcontroller include files */
+    "s", "sa", /* Unix assembler */
+    "S",   /* cpp-processed Unix assembler */
+    "src", /* BSO/Tasking C compiler output */
+    NULL
+  };
 
 /* Note that .c and .h can be considered C++, if the --c++ flag was
    given, or if the `class' keyowrd is met inside the file.
    That is why default_C_entries is called for these. */
-char *default_C_suffixes [] =
+static char *default_C_suffixes [] =
   { "c", "h", NULL };
 
-char *Cplusplus_suffixes [] =
+static char *Cplusplus_suffixes [] =
   { "C", "c++", "cc", "cpp", "cxx", "H", "h++", "hh", "hpp", "hxx",
     "M",                       /* Objective C++ */
     "pdb",                     /* Postscript with C syntax */
     NULL };
 
-char *Cjava_suffixes [] =
+static char *Cjava_suffixes [] =
   { "java", NULL };
 
-char *Cobol_suffixes [] =
+static char *Cobol_suffixes [] =
   { "COB", "cob", NULL };
 
-char *Cstar_suffixes [] =
+static char *Cstar_suffixes [] =
   { "cs", "hs", NULL };
 
-char *Erlang_suffixes [] =
+static char *Erlang_suffixes [] =
   { "erl", "hrl", NULL };
 
-char *Fortran_suffixes [] =
+static char *Fortran_suffixes [] =
   { "F", "f", "f90", "for", NULL };
 
-char *Lisp_suffixes [] =
+static char *Lisp_suffixes [] =
   { "cl", "clisp", "el", "l", "lisp", "LSP", "lsp", "ml", NULL };
 
-char *Makefile_filenames [] =
+static char *Makefile_filenames [] =
   { "Makefile", "makefile", "GNUMakefile", "Makefile.in", "Makefile.am", NULL};
 
-char *Pascal_suffixes [] =
+static char *Pascal_suffixes [] =
   { "p", "pas", NULL };
 
-char *Perl_suffixes [] =
+static char *Perl_suffixes [] =
   { "pl", "pm", NULL };
-char *Perl_interpreters [] =
+
+static char *Perl_interpreters [] =
   { "perl", "@PERL@", NULL };
 
-char *PHP_suffixes [] =
+static char *PHP_suffixes [] =
   { "php", "php3", "php4", NULL };
 
-char *plain_C_suffixes [] =
+static char *plain_C_suffixes [] =
   { "lm",                      /* Objective lex file */
     "m",                       /* Objective C file */
     "pc",                      /* Pro*C file */
      NULL };
 
-char *Postscript_suffixes [] =
+static char *Postscript_suffixes [] =
   { "ps", "psw", NULL };       /* .psw is for PSWrap */
 
-char *Prolog_suffixes [] =
+static char *Prolog_suffixes [] =
   { "prolog", NULL };
 
-char *Python_suffixes [] =
+static char *Python_suffixes [] =
   { "py", NULL };
 
 /* Can't do the `SCM' or `scm' prefix with a version number. */
-char *Scheme_suffixes [] =
+static char *Scheme_suffixes [] =
   { "oak", "sch", "scheme", "SCM", "scm", "SM", "sm", "ss", "t", NULL };
 
-char *TeX_suffixes [] =
+static char *TeX_suffixes [] =
   { "bib", "clo", "cls", "ltx", "sty", "TeX", "tex", NULL };
 
-char *Texinfo_suffixes [] =
+static char *Texinfo_suffixes [] =
   { "texi", "texinfo", "txi", NULL };
 
-char *Yacc_suffixes [] =
+static char *Yacc_suffixes [] =
   { "y", "y++", "ym", "yxx", "yy", NULL }; /* .ym is Objective yacc file */
 
 /*
@@ -574,7 +580,7 @@ char *Yacc_suffixes [] =
  * name.  I just didn't.
  */
 
-language lang_names [] =
+static language lang_names [] =
 {
   { "ada",               Ada_funcs,            NULL, Ada_suffixes,             NULL },
   { "asm",               Asm_labels,           NULL, Asm_suffixes,             NULL },
@@ -1042,7 +1048,6 @@ main (argc, argv)
              }
          }
          break;
-#ifdef ETAGS_REGEXPS
        case 'r':
          argbuffer[current_arg].arg_type = at_regexp;
          argbuffer[current_arg].what = optarg;
@@ -1058,7 +1063,6 @@ main (argc, argv)
          argbuffer[current_arg].what = optarg;
          ++current_arg;
          break;
-#endif /* ETAGS_REGEXPS */
        case 'V':
          print_version ();
          break;
@@ -1072,19 +1076,16 @@ main (argc, argv)
        case 'T':
          typedefs = typedefs_or_cplusplus = TRUE;
          break;
-#if (!CTAGS)
          /* Etags options */
        case 'i':
          included_files[nincluded_files++] = optarg;
          break;
-#else /* CTAGS */
          /* Ctags options. */
        case 'B': searchar = '?';       break;
        case 'u': update = TRUE;        break;
        case 'v': vgrind_style = TRUE;  /*FALLTHRU*/
        case 'x': cxref_style = TRUE;   break;
        case 'w': no_warnings = TRUE;   break;
-#endif /* CTAGS */
        default:
          suggest_asking_for_help ();
        }
@@ -1193,22 +1194,17 @@ main (argc, argv)
   free_patterns ();
 #endif /* ETAGS_REGEXPS */
 
-  if (!CTAGS)
-    {
-      while (nincluded_files-- > 0)
-       fprintf (tagf, "\f\n%s,include\n", *included_files++);
-
-      fclose (tagf);
-      exit (GOOD);
-    }
-
-  /* If CTAGS, we are here.  process_file did not write the tags yet,
-     because we want them ordered.  Let's do it now. */
-  if (cxref_style)
+  if (!CTAGS || cxref_style)
     {
       put_entries (head);
       free_tree (head);
       head = NULL;
+      if (!CTAGS)
+       while (nincluded_files-- > 0)
+         fprintf (tagf, "\f\n%s,include\n", *included_files++);
+
+      if (fclose (tagf) == EOF)
+       pfatal (tagfile);
       exit (GOOD);
     }
 
@@ -1234,12 +1230,13 @@ main (argc, argv)
   put_entries (head);
   free_tree (head);
   head = NULL;
-  fclose (tagf);
+  if (fclose (tagf) == EOF)
+    pfatal (tagfile);
 
   if (update)
     {
       char cmd[BUFSIZ];
-      sprintf (cmd, "sort %s -o %s", tagfile, tagfile);
+      sprintf (cmd, "sort -o %s %s", tagfile, tagfile);
       exit (system (cmd));
     }
   return GOOD;
@@ -1379,6 +1376,7 @@ process_file (file)
   compressor *compr;
   char *compressed_name, *uncompressed_name;
   char *ext, *real_name;
+  int retval;
 
 
   canonicalize_filename (file);
@@ -1398,7 +1396,7 @@ process_file (file)
       uncompressed_name = savenstr (file, ext - file);
     }
 
-  /* If the canonicalised uncompressed name has already be dealt with,
+  /* If the canonicalized uncompressed name has already be dealt with,
      skip it silently, else add it to the list. */
   {
     typedef struct processed_file
@@ -1486,34 +1484,29 @@ process_file (file)
       goto exit;
     }
 
-  find_entries (uncompressed_name, inf);
-
-  if (real_name == compressed_name)
-    pclose (inf);
+  curfile = uncompressed_name;
+  curfiledir = absolute_dirname (curfile, cwd);
+  if (filename_is_absolute (curfile))
+    {
+      /* file is an absolute file name.  Canonicalize it. */
+      curtagfname = absolute_filename (curfile, NULL);
+    }
   else
-    fclose (inf);
-
-  if (!CTAGS)
     {
-      char *filename;
-
-      if (filename_is_absolute (uncompressed_name))
-       {
-         /* file is an absolute file name.  Canonicalise it. */
-         filename = absolute_filename (uncompressed_name, cwd);
-       }
-      else
-       {
-         /* file is a file name relative to cwd.  Make it relative
-            to the directory of the tags file. */
-         filename = relative_filename (uncompressed_name, tagfiledir);
-       }
-      fprintf (tagf, "\f\n%s,%d\n", filename, total_size_of_entries (head));
-      free (filename);
-      put_entries (head);
-      free_tree (head);
-      head = NULL;
+      /* file is a file name relative to cwd.  Make it relative
+        to the directory of the tags file. */
+      curtagfname = relative_filename (curfile, tagfiledir);
     }
+  nocharno = FALSE;            /* use char position when making tags */
+  find_entries (curfile, inf);
+
+  free (curfiledir);
+  if (real_name == compressed_name)
+    retval = pclose (inf);
+  else
+    retval = fclose (inf);
+  if (retval < 0)
+    pfatal (file);
 
  exit:
   if (compressed_name) free(compressed_name);
@@ -1552,7 +1545,7 @@ init ()
  * This routine opens the specified file and calls the function
  * which finds the function and type definitions.
  */
-node *last_node = NULL;
+static node *last_node = NULL;
 
 static void
 find_entries (file, inf)
@@ -1563,13 +1556,6 @@ find_entries (file, inf)
   language *lang;
   node *old_last_node;
 
-  /* Memory leakage here: the string pointed by curfile is
-     never released, because curfile is copied into np->file
-     for each node, to be used in CTAGS mode.  The amount of
-     memory leaked here is the sum of the lengths of the
-     file names. */
-  curfile = savestr (file);
-
   /* If user specified a language, use it. */
   lang = forced_lang;
   if (lang != NULL && lang->function != NULL)
@@ -1661,8 +1647,8 @@ pfnote (name, is_func, linestart, linelen, lno, cno)
   /* If ctags mode, change name "main" to M<thisfilename>. */
   if (CTAGS && !cxref_style && streq (name, "main"))
     {
-      register char *fp = etags_strrchr (curfile, '/');
-      np->name = concat ("M", fp == NULL ? curfile : fp + 1, "");
+      register char *fp = etags_strrchr (curtagfname, '/');
+      np->name = concat ("M", fp == NULL ? curtagfname : fp + 1, "");
       fp = etags_strrchr (np->name, '.');
       if (fp != NULL && fp[1] != '\0' && fp[2] == '\0')
        fp[0] = '\0';
@@ -1670,15 +1656,18 @@ pfnote (name, is_func, linestart, linelen, lno, cno)
   else
     np->name = name;
   np->been_warned = FALSE;
-  np->file = curfile;
+  np->file = curtagfname;
   np->is_func = is_func;
   np->lno = lno;
-  /* Our char numbers are 0-base, because of C language tradition?
-     ctags compatibility?  old versions compatibility?   I don't know.
-     Anyway, since emacs's are 1-base we expect etags.el to take care
-     of the difference.  If we wanted to have 1-based numbers, we would
-     uncomment the +1 below. */
-  np->cno = cno /* + 1 */ ;
+  if (nocharno)
+    np->cno = invalidcharno;
+  else
+    /* Our char numbers are 0-base, because of C language tradition?
+       ctags compatibility?  old versions compatibility?   I don't know.
+       Anyway, since emacs's are 1-base we expect etags.el to take care
+       of the difference.  If we wanted to have 1-based numbers, we would
+       uncomment the +1 below. */
+    np->cno = cno /* + 1 */ ;
   np->left = np->right = NULL;
   if (CTAGS && !cxref_style)
     {
@@ -1771,9 +1760,9 @@ free_tree (np)
 
 /*
  * add_node ()
- *     Adds a node to the tree of nodes.  In etags mode, we don't keep
- *     it sorted; we just keep a linear list.  In ctags mode, maintain
- *     an ordered tree, with no attempt at balancing.
+ *     Adds a node to the tree of nodes.  In etags mode, sort by file
+ *     name.  In ctags mode, sort by tag name.  Make no attempt at
+ *     balancing.
  *
  *     add_node is the only function allowed to add nodes, so it can
  *     maintain state.
@@ -1795,10 +1784,27 @@ add_node (np, cur_node_p)
   if (!CTAGS)
     {
       /* Etags Mode */
-      if (last_node == NULL)
-       fatal ("internal error in add_node", (char *)NULL);
-      last_node->right = np;
-      last_node = np;
+      assert (last_node != NULL);
+      /* For each file name, tags are in a linked sublist on the right
+        pointer.  The first tags of different files are a linked list
+        on the left pointer.  last_node points to the end of the last
+        used sublist. */
+      if (last_node->file == np->file)
+       {
+         /* Let's use the same sublist as the last added node. */
+         last_node->right = np;
+         last_node = np;
+       }
+      else if (streq (cur_node->file, np->file))
+       {
+         /* Scanning the list we found the head of a sublist which is
+            good for us.  Let's scan this sublist. */
+         add_node (np, &cur_node->right);
+       }
+      else
+       /* The head of this sublist is not good for us.  Let's try the
+          next one. */
+       add_node (np, &cur_node->left);
     }
   else
     {
@@ -1837,31 +1843,87 @@ add_node (np, cur_node_p)
 }
 
 \f
+static int total_size_of_entries __P((node *));
+static int number_len __P((long));
+
+/* Length of a number's decimal representation. */
+static int
+number_len (num)
+     long num;
+{
+  int len = 1;
+  while ((num /= 10) > 0)
+    len += 1;
+  return len;
+}
+
+/*
+ * Return total number of characters that put_entries will output for
+ * the nodes in the linked list at the right of the specified node.
+ * This count is irrelevant with etags.el since emacs 19.34 at least,
+ * but is still supplied for backward compatibility.
+ */
+static int
+total_size_of_entries (np)
+     register node *np;
+{
+  register int total = 0;
+
+  for (; np != NULL; np = np->right)
+    {
+      total += strlen (np->pat) + 1;           /* pat\177 */
+      if (np->name != NULL)
+       total += strlen (np->name) + 1;         /* name\001 */
+      total += number_len ((long) np->lno) + 1;        /* lno, */
+      if (np->cno != invalidcharno)            /* cno */
+       total += number_len (np->cno);
+      total += 1;                              /* newline */
+    }
+
+  return total;
+}
+
 static void
 put_entries (np)
      register node *np;
 {
   register char *sp;
+  static char *file = NULL;
 
   if (np == NULL)
     return;
 
   /* Output subentries that precede this one */
-  put_entries (np->left);
+  if (CTAGS)
+    put_entries (np->left);
 
   /* Output this entry */
-
   if (!CTAGS)
     {
+      /* Etags mode */
+      if (file != np->file
+         && (file == NULL || !streq (file, np->file)))
+       {
+         file = np->file;
+         fprintf (tagf, "\f\n%s,%d\n",
+                  file, total_size_of_entries (np));
+       }
+      fputs (np->pat, tagf);
+      fputc ('\177', tagf);
       if (np->name != NULL)
-       fprintf (tagf, "%s\177%s\001%d,%ld\n",
-                np->pat, np->name, np->lno, np->cno);
+       {
+         fputs (np->name, tagf);
+         fputc ('\001', tagf);
+       }
+      fprintf (tagf, "%d,", np->lno);
+      if (np->cno == invalidcharno)
+       fputc ('\n', tagf);
       else
-       fprintf (tagf, "%s\177%d,%ld\n",
-                np->pat, np->lno, np->cno);
+       fprintf (tagf, "%ld\n", np->cno);
     }
   else
     {
+      /* Ctags mode */
       if (np->name == NULL)
        error ("internal error: NULL name in ctags mode.", (char *)NULL);
 
@@ -1899,50 +1961,11 @@ put_entries (np)
        }
     }
 
+
   /* Output subentries that follow this one */
   put_entries (np->right);
-}
-
-/* Length of a number's decimal representation. */
-static int
-number_len (num)
-     long num;
-{
-  int len = 1;
-  while ((num /= 10) > 0)
-    len += 1;
-  return len;
-}
-
-/*
- * Return total number of characters that put_entries will output for
- * the nodes in the subtree of the specified node.  Works only if
- * we are not ctags, but called only in that case.  This count
- * is irrelevant with the new tags.el, but is still supplied for
- * backward compatibility.
- */
-static int
-total_size_of_entries (np)
-     register node *np;
-{
-  register int total;
-
-  if (np == NULL)
-    return 0;
-
-  for (total = 0; np != NULL; np = np->right)
-    {
-      /* Count left subentries. */
-      total += total_size_of_entries (np->left);
-
-      /* Count this entry */
-      total += strlen (np->pat) + 1;
-      total += number_len ((long) np->lno) + 1 + number_len (np->cno) + 1;
-      if (np->name != NULL)
-       total += 1 + strlen (np->name); /* \001name */
-    }
-
-  return total;
+  if (!CTAGS)
+    put_entries (np->left);
 }
 
 \f
@@ -2033,7 +2056,7 @@ PSEUDO,           0,      st_C_gnumacro
 #DEFVAR_,      0,      st_C_gnumacro
 %]
 and replace lines between %< and %> with its output,
-then make in_word_set static. */
+then make in_word_set and C_stab_entry static. */
 /*%<*/
 /* C code produced by gperf version 2.7.1 (19981006 egcs) */
 /* Command-line: gperf -c -k 1,3 -o -p -r -t  */
@@ -2218,7 +2241,7 @@ C_symtype (str, len, c_ext)
  * C functions and variables are recognized using a simple
  * finite automaton.  fvdef is its state variable.
  */
-enum
+static enum
 {
   fvnone,                      /* nothing seen */
   fdefunkey,                   /* Emacs DEFUN keyword seen */
@@ -2232,13 +2255,13 @@ enum
   vignore                      /* var-like: ignore until ';' */
 } fvdef;
 
-bool fvextern;                 /* func or var: extern keyword seen; */
+static bool fvextern;          /* func or var: extern keyword seen; */
 
 /*
  * typedefs are recognized using a simple finite automaton.
  * typdef is its state variable.
  */
-enum
+static enum
 {
   tnone,                       /* nothing seen */
   tkeyseen,                    /* typedef keyword seen */
@@ -2253,7 +2276,7 @@ enum
  * using another simple finite automaton.  `structdef' is its state
  * variable.
  */
-enum
+static enum
 {
   snone,                       /* nothing seen yet,
                                   or in struct body if cblev > 0 */
@@ -2266,12 +2289,12 @@ enum
 /*
  * When objdef is different from onone, objtag is the name of the class.
  */
-char *objtag = "<uninited>";
+static char *objtag = "<uninited>";
 
 /*
  * Yet another little state machine to deal with preprocessor lines.
  */
-enum
+static enum
 {
   dnone,                       /* nothing seen */
   dsharpseen,                  /* '#' seen as first char on line */
@@ -2283,7 +2306,7 @@ enum
  * State machine for Objective C protocols and implementations.
  * Idea by Tom R.Hageman <tom@basil.icce.rug.nl> (1995)
  */
-enum
+static enum
 {
   onone,                       /* nothing seen */
   oprotocol,                   /* @interface or @protocol seen */
@@ -2304,7 +2327,7 @@ enum
  * Use this structure to keep info about the token read, and how it
  * should be tagged.  Used by the make_C_tag function to build a tag.
  */
-struct tok
+static struct tok
 {
   bool valid;
   bool named;
@@ -2314,7 +2337,7 @@ struct tok
   long linepos;
   char *line;
 } token;                       /* latest token read */
-linebuffer token_name;         /* its name */
+static linebuffer token_name;  /* its name */
 
 /*
  * Variables and functions for dealing with nested structures.
@@ -2324,7 +2347,7 @@ static void pushclass_above __P((int, char *, int));
 static void popclass_above __P((int));
 static void write_classname __P((linebuffer *, char *qualifier));
 
-struct {
+static struct {
   char **cname;                        /* nested class names */
   int *cblev;                  /* nested class curly brace level */
   int nl;                      /* class nesting level (elements used) */
@@ -2711,7 +2734,7 @@ consider_token (str, len, c, c_extp, cblev, parlev, is_func_or_var)
  * the line currently read.  By keeping two line buffers, and switching
  * them at end of line, it is possible to use those pointers.
  */
-struct
+static struct
 {
   long linepos;
   linebuffer lb;
@@ -4027,8 +4050,8 @@ Perl_functions (inf)
 
          /* Perhaps I should back cp up one character, so the TAGS table
             doesn't mention (and so depend upon) the following char. */
-         pfnote ((CTAGS) ? savenstr (lb.buffer, cp-lb.buffer) : varname,
-                 FALSE, lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+         pfnote (varname, FALSE,
+                 lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
        }
     }
 }
@@ -4036,8 +4059,9 @@ Perl_functions (inf)
 
 /*
  * Python support
- * Look for /^def[ \t\n]+[^ \t\n(:]+/ or /^class[ \t\n]+[^ \t\n(:]+/
+ * Look for /^[\t]*def[ \t\n]+[^ \t\n(:]+/ or /^class[ \t\n]+[^ \t\n(:]+/
  * Idea by Eric S. Raymond <esr@thyrsus.com> (1997)
+ * More ideas by seb bacon <seb@jamkit.com> (2002)
  */
 static void
 Python_functions (inf)
@@ -4046,13 +4070,17 @@ Python_functions (inf)
   register char *cp;
 
   LOOP_ON_INPUT_LINES (inf, lb, cp)
-    if (LOOKING_AT (cp, "def") || LOOKING_AT (cp, "class"))
-      {
-       while (!notinname (*cp) && *cp != ':')
-         cp++;
-       pfnote (NULL, TRUE,
-               lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
-      }
+    {
+      cp = skip_spaces (cp);
+      if (LOOKING_AT (cp, "def") || LOOKING_AT (cp, "class"))
+       {
+         char *name = cp;
+         while (!notinname (*cp) && *cp != ':')
+           cp++;
+         pfnote (savenstr (name, cp-name), TRUE,
+                 lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+       }
+    }
 }
 
 \f
@@ -4070,18 +4098,19 @@ static void
 PHP_functions (inf)
      FILE *inf;
 {
-  register char *cp;
+  register char *cp, *name;
   bool search_identifier = FALSE;
 
   LOOP_ON_INPUT_LINES (inf, lb, cp)
     {
       cp = skip_spaces (cp);
+      name = cp;
       if (search_identifier
          && *cp != '\0')
        {
          while (!notinname (*cp))
            cp++;
-         pfnote (NULL, TRUE,
+         pfnote (savenstr (name, cp-name), TRUE,
                  lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
          search_identifier = FALSE;
        }
@@ -4091,9 +4120,10 @@ PHP_functions (inf)
            cp = skip_spaces (cp+1);
          if(*cp != '\0')
            {
+             name = cp;
              while (!notinname (*cp))
                cp++;
-             pfnote (NULL, TRUE,
+             pfnote (savenstr (name, cp-name), TRUE,
                      lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
            }
          else
@@ -4103,9 +4133,10 @@ PHP_functions (inf)
        {
          if (*cp != '\0')
            {
+             name = cp;
              while (*cp != '\0' && !iswhite (*cp))
                cp++;
-             pfnote (NULL, FALSE,
+             pfnote (savenstr (name, cp-name), FALSE,
                      lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
            }
          else
@@ -4117,18 +4148,20 @@ PHP_functions (inf)
               && (*cp == '"' || *cp == '\''))
        {
          char quote = *cp++;
+         name = cp;
          while (*cp != quote && *cp != '\0')
            cp++;
-         pfnote (NULL, FALSE,
+         pfnote (savenstr (name, cp-name), FALSE,
                  lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
        }
       else if (members
               && LOOKING_AT (cp, "var")
               && *cp == '$')
        {
+         name = cp;
          while (!notinname(*cp))
            cp++;
-         pfnote (NULL, FALSE,
+         pfnote (savenstr (name, cp-name), FALSE,
                  lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
        }
     }
@@ -4507,12 +4540,12 @@ struct TEX_tabent
   int len;
 };
 
-struct TEX_tabent *TEX_toktab = NULL;  /* Table with tag tokens */
+static struct TEX_tabent *TEX_toktab = NULL; /* Table with tag tokens */
 
 /* Default set of control sequences to put into TEX_toktab.
    The value of environment var TEXTAGS is prepended to this.  */
 
-char *TEX_defenv = "\
+static char *TEX_defenv = "\
 :chapter:section:subsection:subsubsection:eqno:label:ref:cite:bibitem\
 :part:appendix:entry:index";
 
@@ -4520,9 +4553,9 @@ static void TEX_mode __P((FILE *));
 static struct TEX_tabent *TEX_decode_env __P((char *, char *));
 static int TEX_Token __P((char *));
 
-char TEX_esc = '\\';
-char TEX_opgrp = '{';
-char TEX_clgrp = '}';
+static char TEX_esc = '\\';
+static char TEX_opgrp = '{';
+static char TEX_clgrp = '}';
 
 /*
  * TeX/LaTeX scanning loop.
@@ -4554,13 +4587,16 @@ TeX_commands (inf)
          i = TEX_Token (lasthit);
          if (i >= 0)
            {
-             /* We seem to include the TeX command in the tag name.
              register char *p;
-             for (p = lasthit + TEX_toktab[i].len;
-                  *p != '\0' && *p != TEX_clgrp;
+             for (lasthit += TEX_toktab[i].len;
+                  *lasthit == TEX_esc || *lasthit == TEX_opgrp;
+                  lasthit++)
+               continue;
+             for (p = lasthit;
+                  !iswhite (*p) && *p != TEX_opgrp && *p != TEX_clgrp;
                   p++)
-               continue; */
-             pfnote (/*savenstr (lasthit, p-lasthit)*/ (char *)NULL, TRUE,
+               continue;
+             pfnote (savenstr (lasthit, p-lasthit), TRUE,
                      lb.buffer, lb.len, lineno, linecharno);
              break;            /* We only tag a line once */
            }
@@ -5406,51 +5442,94 @@ readline (lbp, stream)
 {
   /* Read new line. */
   long result = readline_internal (lbp, stream);
+
+  /* Honour #line directives. */
+  if (!no_line_directive
+      && result > 12 && strneq (lbp->buffer, "#line ", 6))
+    {
+      int start, lno;
+
+      if (sscanf (lbp->buffer, "#line %d \"%n", &lno, &start) == 1)
+       {
+         char *endp = lbp->buffer + start;
+
+         while ((endp = etags_strchr (endp, '"')) != NULL
+                && endp[-1] == '\\')
+           endp++;
+         if (endp != NULL)
+           {
+             char *absname, *name = lbp->buffer + start;
+             *endp = '\0';
+
+             canonicalize_filename(name); /* for DOS */
+             absname = absolute_filename (name, curfiledir);
+             if (filename_is_absolute (name)
+                 || filename_is_absolute (curfile))
+               name = absname;
+             else
+               {
+                 name = relative_filename (absname, tagfiledir);
+                 free (absname);
+               }
+
+             if (streq (curtagfname, name))
+               free (name);
+             else
+               curtagfname = name;
+             lineno = lno;
+             nocharno = TRUE;  /* do not use char position for tags */
+             return readline (lbp, stream);
+           }
+       }
+    }
+
 #ifdef ETAGS_REGEXPS
-  int match;
-  pattern *pp;
+  {
+    int match;
+    pattern *pp;
 
-  /* Match against relevant patterns. */
-  if (lbp->len > 0)
-    for (pp = p_head; pp != NULL; pp = pp->p_next)
-      {
-       /* Only use generic regexps or those for the current language. */
-       if (pp->lang != NULL && pp->lang != curlang)
-         continue;
+    /* Match against relevant patterns. */
+    if (lbp->len > 0)
+      for (pp = p_head; pp != NULL; pp = pp->p_next)
+       {
+         /* Only use generic regexps or those for the current language. */
+         if (pp->lang != NULL && pp->lang != curlang)
+           continue;
 
-       match = re_match (pp->pat, lbp->buffer, lbp->len, 0, &pp->regs);
-       switch (match)
-         {
-         case -2:
-           /* Some error. */
-           if (!pp->error_signaled)
-             {
-               error ("error while matching \"%s\"", pp->regex);
-               pp->error_signaled = TRUE;
-             }
-           break;
-         case -1:
-           /* No match. */
-           break;
-         default:
-           /* Match occurred.  Construct a tag. */
-           if (pp->name_pattern[0] != '\0')
-             {
-               /* Make a named tag. */
-               char *name = substitute (lbp->buffer,
-                                        pp->name_pattern, &pp->regs);
-               if (name != NULL)
-                 pfnote (name, TRUE, lbp->buffer, match, lineno, linecharno);
-             }
-           else
-             {
-               /* Make an unnamed tag. */
-               pfnote ((char *)NULL, TRUE,
-                       lbp->buffer, match, lineno, linecharno);
-             }
-           break;
-         }
-      }
+         match = re_match (pp->pat, lbp->buffer, lbp->len, 0, &pp->regs);
+         switch (match)
+           {
+           case -2:
+             /* Some error. */
+             if (!pp->error_signaled)
+               {
+                 error ("error while matching \"%s\"", pp->regex);
+                 pp->error_signaled = TRUE;
+               }
+             break;
+           case -1:
+             /* No match. */
+             break;
+           default:
+             /* Match occurred.  Construct a tag. */
+             if (pp->name_pattern[0] != '\0')
+               {
+                 /* Make a named tag. */
+                 char *name = substitute (lbp->buffer,
+                                          pp->name_pattern, &pp->regs);
+                 if (name != NULL)
+                   pfnote (name, TRUE, lbp->buffer, match, lineno, linecharno);
+               }
+             else
+               {
+                 /* Make an unnamed tag. */
+                 pfnote ((char *)NULL, TRUE,
+                         lbp->buffer, match, lineno, linecharno);
+               }
+             break;
+           }
+       }
+  }
 #endif /* ETAGS_REGEXPS */
 
   return result;
@@ -5826,7 +5905,7 @@ linebuffer_setlen (lbp, toksize)
 }
 
 /* Like malloc but get fatal error if memory is exhausted.  */
-PTR
+static PTR
 xmalloc (size)
      unsigned int size;
 {
@@ -5836,7 +5915,7 @@ xmalloc (size)
   return result;
 }
 
-PTR
+static PTR
 xrealloc (ptr, size)
      char *ptr;
      unsigned int size;