diff lib-src/etags.c @ 43723:5cd450d9f443

* etags.c: Honour #line directives. (no_line_directive): New global var; set it for old behaviour. (main): Remove some #ifdef in the getopt switch. (add_node, put_entries): Code added to merge different chunks of nodes referring to the same file. Currently the tags are just appended, without any check for duplicates. (Perl_functions): Do not special case ctags. (readline): Identify #line directives and do the right thing. (nocharno, invalidcharno): New global vars. (process_file): Reset nocharno. (readline): Set nocharno. (pfnote): Read nocharno and maybe put invalidcharno in node. (total_size_of_entries, put_entries): Use invalidcharno. * etags.c: Keep the whole tag table in memory, even in etags mode. (main): Call put_entries here even in CTAGS mode. (main, process_file): Check the return values of fclose and pclose. (process_file): Do not call put_entries after parsing each file. (process_file): Canonicalise file names even for ctags. (process_file): Set curfile here... (find_entries): ... not here any more. (add_node): In etags mode, build a linked list of entries (on right pointer) for each file, and link the first entry of each file on left nodes. (put_entries): Print here the name of the file. (put_entries): Print the entries starting from the first file. (number_len, total_size_of_entries): Define these only iin etags mode, make the second work only on the right nodes. * etags.c: Make all global variables static.
author Francesco Potortì <pot@gnu.org>
date Tue, 05 Mar 2002 11:28:26 +0000
parents 7d2776273a81
children 492830d6693c
line wrap: on
line diff
--- a/lib-src/etags.c	Tue Mar 05 11:27:51 2002 +0000
+++ b/lib-src/etags.c	Tue Mar 05 11:28:26 2002 +0000
@@ -33,7 +33,7 @@
  *	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 14.39";
 
 #define	TRUE	1
 #define	FALSE	0
@@ -299,13 +299,11 @@
 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,35 @@
 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));
 
 
-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 */
-
-char *curfile;			/* current input file name */
-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 */
-
-node *head;			/* the head of the binary tree of tags */
-
-linebuffer lb;			/* the current line */
+static char searchar = '/';	/* use /.../ searches */
+
+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 */
+
+static char *curfile;		/* current input file name */
+static language *curlang;	/* current language */
+
+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;
+
+static node *head;		/* the head of the binary tree of tags */
+
+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 +383,60 @@
   /* 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 +456,15 @@
 } 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 +479,96 @@
  */
 
 /* 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 +578,7 @@
  * 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 +1046,6 @@
 	      }
 	  }
 	  break;
-#ifdef ETAGS_REGEXPS
 	case 'r':
 	  argbuffer[current_arg].arg_type = at_regexp;
 	  argbuffer[current_arg].what = optarg;
@@ -1058,7 +1061,6 @@
 	  argbuffer[current_arg].what = optarg;
 	  ++current_arg;
 	  break;
-#endif /* ETAGS_REGEXPS */
 	case 'V':
 	  print_version ();
 	  break;
@@ -1072,19 +1074,16 @@
 	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 +1192,17 @@
   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,7 +1228,8 @@
   put_entries (head);
   free_tree (head);
   head = NULL;
-  fclose (tagf);
+  if (fclose (tagf) == EOF)
+    pfatal (tagfile);
 
   if (update)
     {
@@ -1379,6 +1374,7 @@
   compressor *compr;
   char *compressed_name, *uncompressed_name;
   char *ext, *real_name;
+  int retval;
 
 
   canonicalize_filename (file);
@@ -1486,34 +1482,26 @@
       goto exit;
     }
 
+  if (filename_is_absolute (uncompressed_name))
+    {
+      /* file is an absolute file name.  Canonicalise it. */
+      curfile = absolute_filename (uncompressed_name, cwd);
+    }
+  else
+    {
+      /* file is a file name relative to cwd.  Make it relative
+	 to the directory of the tags file. */
+      curfile = relative_filename (uncompressed_name, tagfiledir);
+    }
+  nocharno = FALSE;		/* use char position when making tags */
   find_entries (uncompressed_name, inf);
 
   if (real_name == compressed_name)
-    pclose (inf);
+    retval = pclose (inf);
   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;
-    }
+    retval = fclose (inf);
+  if (retval < 0)
+    pfatal (file);
 
  exit:
   if (compressed_name) free(compressed_name);
@@ -1552,7 +1540,7 @@
  * 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 +1551,6 @@
   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)
@@ -1673,12 +1654,15 @@
   np->file = curfile;
   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 +1755,9 @@
 
 /*
  * 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 +1779,27 @@
   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 +1838,89 @@
 }
 
 
+#if !CTAGS
+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;
+}
+#endif
+
 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 +1958,11 @@
 	}
     }
 
+
   /* 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);
 }
 
 
@@ -2033,7 +2053,7 @@
 #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 +2238,7 @@
  * 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 +2252,13 @@
   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 +2273,7 @@
  * 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 +2286,12 @@
 /*
  * 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 +2303,7 @@
  * 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 +2324,7 @@
  * 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 +2334,7 @@
   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 +2344,7 @@
 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 +2731,7 @@
  * 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 +4047,8 @@
 
  	  /* 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);
 	}
     }
 }
@@ -4507,12 +4527,12 @@
   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 +4540,9 @@
 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.
@@ -5406,51 +5426,78 @@
 {
   /* Read new line. */
   long result = readline_internal (lbp, stream);
+
+  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)
+	    {
+	      int len = endp - (lbp->buffer + start);
+
+	      if (!strneq (curfile, lbp->buffer + start, len))
+		curfile = savenstr (lbp->buffer + start, len);
+	      lineno = lno;
+	      nocharno = TRUE;	/* do not use char position for tags */
+	      return readline (lbp, stream);
+	    }
+	}
+    }
 #ifdef ETAGS_REGEXPS
-  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 = 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;
-	  }
-      }
+  {
+    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 = 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 +5873,7 @@
 }
 
 /* Like malloc but get fatal error if memory is exhausted.  */
-PTR
+static PTR
 xmalloc (size)
      unsigned int size;
 {
@@ -5836,7 +5883,7 @@
   return result;
 }
 
-PTR
+static PTR
 xrealloc (ptr, size)
      char *ptr;
      unsigned int size;