]> code.delx.au - gnu-emacs/blobdiff - lib-src/etags.c
Add "-*- makefile -*-" mode tag.
[gnu-emacs] / lib-src / etags.c
index 98c3440704407876de7a174396e24230d2e0f7c8..e9fab1be3ee51b6a292f4f6ef085733522916714 100644 (file)
@@ -1,22 +1,22 @@
 /* Tags file maker to go with GNU Emacs           -*- coding: latin-1 -*-
-   Copyright (C) 1984, 1987-1989, 1993-1995, 1998-2001
+   Copyright (C) 1984, 1987-1989, 1993-1995, 1998-2001, 2002
    Free Software Foundation, Inc. and Ken Arnold
 
-This file is not considered part of GNU Emacs.
+ This file is not considered part of GNU Emacs.
 
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
 
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
 
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software Foundation,
-Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
 
 /*
  * Authors:
@@ -27,13 +27,15 @@ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
  * 1989        Sam Kendall added C++.
  * 1992 Joseph B. Wells improved C and C++ parsing.
  * 1993        Francesco Potortì reorganised C and C++.
- * 1994        Regexp tags by Tom Tromey.
- * 2001 Nested classes by Francesco Potortì (ideas by Mykola Dzyuba).
+ * 1994        Line-by-line regexp tags by Tom Tromey.
+ * 2001 Nested classes by Francesco Potortì (concept by Mykola Dzyuba).
+ * 2002 #line directives by Francesco Potortì.
+ *
+ * Francesco Potortì <pot@gnu.org> has maintained and improved it since 1993.
  *
- *     Francesco Potortì <pot@gnu.org> has maintained it since 1993.
  */
 
-char pot_etags_version[] = "@(#) pot revision number is 15.2";
+char pot_etags_version[] = "@(#) pot revision number is 16.56";
 
 #define        TRUE    1
 #define        FALSE   0
@@ -185,15 +187,17 @@ If you want regular expression support, you should delete this notice and
 #endif
 
 #define streq(s,t)     (assert((s)!=NULL || (t)!=NULL), !strcmp (s, t))
+#define strcaseeq(s,t) (assert((s)!=NULL && (t)!=NULL), !etags_strcasecmp (s, t))
 #define strneq(s,t,n)  (assert((s)!=NULL || (t)!=NULL), !strncmp (s, t, n))
+#define strncaseeq(s,t,n) (assert((s)!=NULL && (t)!=NULL), !etags_strncasecmp (s, t, n))
 
 #define CHARS 256              /* 2^sizeof(char) */
 #define CHAR(x)                ((unsigned int)(x) & (CHARS - 1))
-#define        iswhite(c)      (_wht[CHAR(c)]) /* c is white */
-#define notinname(c)   (_nin[CHAR(c)]) /* c is not in a name */
-#define        begtoken(c)     (_btk[CHAR(c)]) /* c can start token */
-#define        intoken(c)      (_itk[CHAR(c)]) /* c can be in token */
-#define        endtoken(c)     (_etk[CHAR(c)]) /* c ends tokens */
+#define        iswhite(c)      (_wht[CHAR(c)]) /* c is white (see white) */
+#define notinname(c)   (_nin[CHAR(c)]) /* c is not in a name (see nonam) */
+#define        begtoken(c)     (_btk[CHAR(c)]) /* c can start token (see begtk) */
+#define        intoken(c)      (_itk[CHAR(c)]) /* c can be in token (see midtk) */
+#define        endtoken(c)     (_etk[CHAR(c)]) /* c ends tokens (see endtk) */
 
 #define ISALNUM(c)     isalnum (CHAR(c))
 #define ISALPHA(c)     isalpha (CHAR(c))
@@ -228,29 +232,45 @@ typedef void Lang_function __P((FILE *));
 
 typedef struct
 {
-  char *suffix;
-  char *command;               /* Takes one arg and decompresses to stdout */
+  char *suffix;                        /* file name suffix for this compressor */
+  char *command;               /* takes one arg and decompresses to stdout */
 } compressor;
 
 typedef struct
 {
-  char *name;
-  Lang_function *function;
-  char **filenames;
-  char **suffixes;
-  char **interpreters;
+  char *name;                  /* language name */
+  char *help;                   /* detailed help for the language */
+  Lang_function *function;     /* parse function */
+  char **suffixes;             /* name suffixes of this language's files */
+  char **filenames;            /* names of this language's files */
+  char **interpreters;         /* interpreters for this language */
+  bool metasource;             /* source used to generate other sources */
 } language;
 
+typedef struct fdesc
+{
+  struct fdesc *next;          /* for the linked list */
+  char *infname;               /* uncompressed input file name */
+  char *infabsname;            /* absolute uncompressed input file name */
+  char *infabsdir;             /* absolute dir of input file */
+  char *taggedfname;           /* file name to write in tagfile */
+  language *lang;              /* language of file */
+  char *prop;                  /* file properties to write in tagfile */
+  bool usecharno;              /* etags tags shall contain char number */
+  bool written;                        /* entry written in the tags file */
+} fdesc;
+
 typedef struct node_st
-{                              /* sorting structure            */
-  char *name;                  /* function or type name        */
-  char *file;                  /* file name                    */
-  bool is_func;                        /* use pattern or line no       */
-  bool been_warned;            /* set if noticed dup           */
-  int lno;                     /* line number tag is on        */
+{                              /* sorting structure */
+  struct node_st *left, *right;        /* left and right sons */
+  fdesc *fdp;                  /* description of file to whom tag belongs */
+  char *name;                  /* tag name */
+  char *regex;                 /* search regexp */
+  bool valid;                  /* write this tag on the tag file */
+  bool is_func;                        /* function tag: use regexp in CTAGS mode */
+  bool been_warned;            /* warning already given for duplicated tag */
+  int lno;                     /* line number tag is on */
   long cno;                    /* character number line starts on */
-  char *pat;                   /* search pattern               */
-  struct node_st *left, *right;        /* left and right sons          */
 } node;
 
 /*
@@ -267,6 +287,38 @@ typedef struct
   char *buffer;
 } linebuffer;
 
+/* Used to support mixing of --lang and file names. */
+typedef struct
+{
+  enum {
+    at_language,               /* a language specification */
+    at_regexp,                 /* a regular expression */
+    at_filename,               /* a file name */
+    at_stdin,                  /* read from stdin here */
+    at_end                     /* stop parsing the list */
+  } arg_type;                  /* argument type */
+  language *lang;              /* language associated with the argument */
+  char *what;                  /* the argument itself */
+} argument;
+
+#ifdef ETAGS_REGEXPS
+/* Structure defining a regular expression. */
+typedef struct regexp
+{
+  struct regexp *p_next;       /* pointer to next in list */
+  language *lang;              /* if set, use only for this language */
+  char *pattern;               /* the regexp pattern */
+  char *name;                  /* tag name */
+  struct re_pattern_buffer *pat; /* the compiled pattern */
+  struct re_registers regs;    /* re registers */
+  bool error_signaled;         /* already signaled for this regexp */
+  bool force_explicit_name;    /* do not allow implict tag name */
+  bool ignore_case;            /* ignore case when matching */
+  bool multi_line;             /* do a multi-line match on the whole file */
+} regexp;
+#endif /* ETAGS_REGEXPS */
+
+
 /* Many compilers barf on this:
        Lang_function Ada_funcs;
    so let's write it this way */
@@ -281,38 +333,39 @@ static void Cplusplus_entries __P((FILE *));
 static void Cstar_entries __P((FILE *));
 static void Erlang_functions __P((FILE *));
 static void Fortran_functions __P((FILE *));
-static void Yacc_entries __P((FILE *));
+static void HTML_labels __P((FILE *));
 static void Lisp_functions __P((FILE *));
 static void Makefile_targets __P((FILE *));
 static void Pascal_functions __P((FILE *));
 static void Perl_functions __P((FILE *));
 static void PHP_functions __P((FILE *));
-static void Postscript_functions __P((FILE *));
+static void PS_functions __P((FILE *));
 static void Prolog_functions __P((FILE *));
 static void Python_functions __P((FILE *));
 static void Scheme_functions __P((FILE *));
 static void TeX_commands __P((FILE *));
 static void Texinfo_nodes __P((FILE *));
+static void Yacc_entries __P((FILE *));
 static void just_read_file __P((FILE *));
 
 static void print_language_names __P((void));
 static void print_version __P((void));
-static void print_help __P((void));
+static void print_help __P((argument *));
 int main __P((int, char **));
 
 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 long readline __P((linebuffer *, FILE *));
+static language *get_language_from_filename __P((char *, bool));
+static void readline __P((linebuffer *, FILE *));
 static long readline_internal __P((linebuffer *, FILE *));
 static bool nocase_tail __P((char *));
-static char *get_tag __P((char *));
+static void get_tag __P((char *, char **));
 
 #ifdef ETAGS_REGEXPS
-static void analyse_regex __P((char *, bool));
-static void add_regex __P((char *, bool, language *));
-static void free_patterns __P((void));
+static void analyse_regex __P((char *));
+static void free_regexps __P((void));
+static void regex_tag_multiline __P((void));
 #endif /* ETAGS_REGEXPS */
 static void error __P((const char *, const char *));
 static void suggest_asking_for_help __P((void));
@@ -321,12 +374,14 @@ static void pfatal __P((char *));
 static void add_node __P((node *, node **));
 
 static void init __P((void));
-static void initbuffer __P((linebuffer *));
-static void find_entries __P((char *, FILE *));
+static void process_file_name __P((char *, language *));
+static void process_file __P((FILE *, char *, language *));
+static void find_entries __P((FILE *));
 static void free_tree __P((node *));
+static void free_fdesc __P((fdesc *));
 static void pfnote __P((char *, bool, char *, int, int, long));
-static void new_pfnote __P((char *, int, bool, char *, int, int, long));
-static void process_file __P((char *));
+static void make_tag __P((char *, int, bool, char *, int, int, long));
+static void invalidate_nodes __P((fdesc *, node **));
 static void put_entries __P((node *));
 
 static char *concat __P((char *, char *, char *));
@@ -336,12 +391,15 @@ static char *savenstr __P((char *, int));
 static char *savestr __P((char *));
 static char *etags_strchr __P((const char *, int));
 static char *etags_strrchr __P((const char *, int));
+static int etags_strcasecmp __P((const char *, const char *));
+static int etags_strncasecmp __P((const char *, const char *, int));
 static char *etags_getcwd __P((void));
 static char *relative_filename __P((char *, char *));
 static char *absolute_filename __P((char *, char *));
 static char *absolute_dirname __P((char *, char *));
 static bool filename_is_absolute __P((char *f));
 static void canonicalize_filename __P((char *));
+static void linebuffer_init __P((linebuffer *));
 static void linebuffer_setlen __P((linebuffer *, int));
 static PTR xmalloc __P((unsigned int));
 static PTR xrealloc __P((char *, unsigned int));
@@ -355,21 +413,21 @@ static char *cwd;         /* current working directory */
 static char *tagfiledir;       /* directory of tagfile */
 static FILE *tagf;             /* ioptr for tags file */
 
-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 */
-
+static fdesc *fdhead;          /* head of file description list */
+static fdesc *curfdp;          /* current file description */
 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 node *nodehead;         /* the head of the binary tree of tags */
+static node *last_node;                /* the last node created */
 
 static linebuffer lb;          /* the current line */
+static linebuffer filebuf;     /* a buffer containing the whole file */
+static linebuffer token_name;  /* a buffer containing a tag name */
 
 /* boolean "functions" (see init)      */
 static bool _wht[CHARS], _nin[CHARS], _itk[CHARS], _btk[CHARS], _etk[CHARS];
@@ -377,7 +435,7 @@ static char
   /* white chars */
   *white = " \f\t\n\r\v",
   /* not in a name */
-  *nonam = " \f\t\n\r()=,;",
+  *nonam = " \f\t\n\r()=,;",   /* look at make_tag before modifying! */
   /* token ending chars */
   *endtk = " \t\n\r\"'#()[]{}=-+%*/&|^~!<>;,.:?",
   /* token starting chars */
@@ -386,7 +444,7 @@ static char
   *midtk = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz$0123456789";
 
 static bool append_to_tagfile; /* -a: append to tags */
-/* The following four default to TRUE for etags, but to FALSE for ctags.  */
+/* The next four default to TRUE for etags, but to FALSE for ctags.  */
 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++ */
@@ -394,78 +452,71 @@ static bool typedefs_or_cplusplus; /* -T: create tags for C typedefs, level */
 static bool constantypedefs;   /* -d: create tags for C #define, enum */
                                /* constants and variables. */
                                /* -D: opposite of -d.  Default under ctags. */
-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 declarations;      /* --declarations: tag them and extern in C&Co*/
 static bool members;           /* create tags for C member variables */
+static bool no_line_directive; /* ignore #line directives (undocumented) */
 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 ignoreindent;      /* -I: ignore indentation in C */
 static bool packages_only;     /* --packages-only: in Ada, only tag packages*/
 
+#define STDIN 0x1001           /* returned by getopt_long on --parse-stdin */
+static bool parsing_stdin;     /* --parse-stdin used */
+
+#ifdef ETAGS_REGEXPS
+static regexp *p_head;         /* list of all regexps */
+static bool need_filebuf;      /* some regexes are multi-line */
+#else
+# define need_filebuf FALSE
+#endif /* ETAGS_REGEXPS */
+
 #ifdef LONG_OPTIONS
 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 },
   { "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'   },
 #endif /* ETAGS_REGEXPS */
+  { "parse-stdin",        required_argument, NULL,               STDIN },
+  { "version",           no_argument,       NULL,               'V'   },
+
+#if CTAGS /* Etags options */
+  { "backward-search",   no_argument,       NULL,               'B'   },
+  { "cxref",             no_argument,       NULL,               'x'   },
+  { "defines",           no_argument,       NULL,               'd'   },
+  { "globals",           no_argument,       &globals,           TRUE  },
   { "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'   },
+  { "no-warn",           no_argument,       NULL,               'w'   },
+
+#else /* Ctags options */
+  { "append",            no_argument,       NULL,               'a'   },
+  { "no-defines",        no_argument,       NULL,               'D'   },
+  { "no-globals",        no_argument,       &globals,           FALSE },
+  { "include",           required_argument, NULL,               'i'   },
+#endif
   { NULL }
 };
 #endif /* LONG_OPTIONS */
 
-#ifdef ETAGS_REGEXPS
-/* Structure defining a regular expression.  Elements are
-   the compiled pattern, and the name string. */
-typedef struct pattern
-{
-  struct pattern *p_next;
-  language *lang;
-  char *regex;
-  struct re_pattern_buffer *pat;
-  struct re_registers regs;
-  char *name_pattern;
-  bool error_signaled;
-} pattern;
-
-/* List of all regexps. */
-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. */
-static char lc_trans[CHAR_SET_SIZE];
-#endif /* ETAGS_REGEXPS */
-
 static compressor compressors[] =
 {
   { "z", "gzip -d -c"},
@@ -480,12 +531,25 @@ static compressor compressors[] =
  * Language stuff.
  */
 
-/* Non-NULL if language fixed. */
-static language *forced_lang = NULL;
-
 /* Ada code */
 static char *Ada_suffixes [] =
   { "ads", "adb", "ada", NULL };
+static char Ada_help [] =
+"In Ada code, functions, procedures, packages, tasks and types are\n\
+tags.  Use the `--packages-only' option to create tags for\n\
+packages only.\n\
+Ada tag names have suffixes indicating the type of entity:\n\
+       Entity type:    Qualifier:\n\
+       ------------    ----------\n\
+       function        /f\n\
+       procedure       /p\n\
+       package spec    /s\n\
+       package body    /b\n\
+       type            /t\n\
+       task            /k\n\
+Thus, `M-x find-tag <RET> bidule/b <RET>' will go directly to the\n\
+body of the package `bidule', while `M-x find-tag <RET> bidule <RET>'\n\
+will just search for any tag `bidule'.";
 
 /* Assembly code */
 static char *Asm_suffixes [] =
@@ -499,79 +563,187 @@ static char *Asm_suffixes [] =
     "src", /* BSO/Tasking C compiler output */
     NULL
   };
+static char Asm_help [] =
+"In assembler code, labels appearing at the beginning of a line,\n\
+followed by a colon, are tags.";
+
 
 /* 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.
+   given, or if the `class' or `template' keyowrds are met inside the file.
    That is why default_C_entries is called for these. */
 static char *default_C_suffixes [] =
   { "c", "h", NULL };
+static char default_C_help [] =
+"In C code, any C function or typedef is a tag, and so are\n\
+definitions of `struct', `union' and `enum'.  `#define' macro\n\
+definitions and `enum' constants are tags unless you specify\n\
+`--no-defines'.  Global variables are tags unless you specify\n\
+`--no-globals'.  Use of `--no-globals' and `--no-defines'\n\
+can make the tags table file much smaller.\n\
+You can tag function declarations and external variables by\n\
+using `--declarations', and struct members by using `--members'.";
 
 static char *Cplusplus_suffixes [] =
   { "C", "c++", "cc", "cpp", "cxx", "H", "h++", "hh", "hpp", "hxx",
     "M",                       /* Objective C++ */
     "pdb",                     /* Postscript with C syntax */
     NULL };
+static char Cplusplus_help [] =
+"In C++ code, all the tag constructs of C code are tagged.  (Use\n\
+--help --lang=c --lang=c++ for full help.)\n\
+In addition to C tags, member functions are also recognized, and\n\
+optionally member variables if you use the `--members' option.\n\
+Tags for variables and functions in classes are named `CLASS::VARIABLE'\n\
+and `CLASS::FUNCTION'.  `operator' definitions have tag names like\n\
+`operator+'.";
 
 static char *Cjava_suffixes [] =
   { "java", NULL };
+static char Cjava_help [] =
+"In Java code, all the tags constructs of C and C++ code are\n\
+tagged.  (Use --help --lang=c --lang=c++ --lang=java for full help.)";
+
 
 static char *Cobol_suffixes [] =
   { "COB", "cob", NULL };
+static char Cobol_help [] =
+"In Cobol code, tags are paragraph names; that is, any word\n\
+starting in column 8 and followed by a period.";
 
 static char *Cstar_suffixes [] =
   { "cs", "hs", NULL };
 
 static char *Erlang_suffixes [] =
   { "erl", "hrl", NULL };
+static char Erlang_help [] =
+"In Erlang code, the tags are the functions, records and macros\n\
+defined in the file.";
 
 static char *Fortran_suffixes [] =
   { "F", "f", "f90", "for", NULL };
+static char Fortran_help [] =
+"In Fortran code, functions, subroutines and block data are tags.";
+
+static char *HTML_suffixes [] =
+  { "htm", "html", "shtml", NULL };
+static char HTML_help [] =
+"In HTML input files, the tags are the `title' and the `h1', `h2',\n\
+`h3' headers.  Also, tags are `name=' in anchors and all\n\
+occurrences of `id='.";
 
 static char *Lisp_suffixes [] =
   { "cl", "clisp", "el", "l", "lisp", "LSP", "lsp", "ml", NULL };
+static char Lisp_help [] =
+"In Lisp code, any function defined with `defun', any variable\n\
+defined with `defvar' or `defconst', and in general the first\n\
+argument of any expression that starts with `(def' in column zero\n\
+is a tag.";
 
 static char *Makefile_filenames [] =
   { "Makefile", "makefile", "GNUMakefile", "Makefile.in", "Makefile.am", NULL};
+static char Makefile_help [] =
+"In makefiles, targets are tags; additionally, variables are tags\n\
+unless you specify `--no-globals'.";
+
+static char *Objc_suffixes [] =
+  { "lm",                      /* Objective lex file */
+    "m",                       /* Objective C file */
+     NULL };
+static char Objc_help [] =
+"In Objective C code, tags include Objective C definitions for classes,\n\
+class categories, methods and protocols.  Tags for variables and\n\
+functions in classes are named `CLASS::VARIABLE' and `CLASS::FUNCTION'.";
 
 static char *Pascal_suffixes [] =
   { "p", "pas", NULL };
+static char Pascal_help [] =
+"In Pascal code, the tags are the functions and procedures defined\n\
+in the file.";
 
 static char *Perl_suffixes [] =
   { "pl", "pm", NULL };
-
 static char *Perl_interpreters [] =
   { "perl", "@PERL@", NULL };
+static char Perl_help [] =
+"In Perl code, the tags are the packages, subroutines and variables\n\
+defined by the `package', `sub', `my' and `local' keywords.  Use\n\
+`--globals' if you want to tag global variables.  Tags for\n\
+subroutines are named `PACKAGE::SUB'.  The name for subroutines\n\
+defined in the default package is `main::SUB'.";
 
 static char *PHP_suffixes [] =
   { "php", "php3", "php4", NULL };
+static char PHP_help [] =
+"In PHP code, tags are functions, classes and defines.  When using\n\
+the `--members' option, vars are tags too.";
 
 static char *plain_C_suffixes [] =
-  { "lm",                      /* Objective lex file */
-    "m",                       /* Objective C file */
-    "pc",                      /* Pro*C file */
+  { "pc",                      /* Pro*C file */
      NULL };
 
-static char *Postscript_suffixes [] =
+static char *PS_suffixes [] =
   { "ps", "psw", NULL };       /* .psw is for PSWrap */
+static char PS_help [] =
+"In PostScript code, the tags are the functions.";
 
 static char *Prolog_suffixes [] =
   { "prolog", NULL };
+static char Prolog_help [] =
+"In Prolog code, tags are predicates and rules at the beginning of\n\
+line.";
 
 static char *Python_suffixes [] =
   { "py", NULL };
+static char Python_help [] =
+"In Python code, `def' or `class' at the beginning of a line\n\
+generate a tag.";
 
 /* Can't do the `SCM' or `scm' prefix with a version number. */
 static char *Scheme_suffixes [] =
   { "oak", "sch", "scheme", "SCM", "scm", "SM", "sm", "ss", "t", NULL };
+static char Scheme_help [] =
+"In Scheme code, tags include anything defined with `def' or with a\n\
+construct whose name starts with `def'.  They also include\n\
+variables set with `set!' at top level in the file.";
 
 static char *TeX_suffixes [] =
   { "bib", "clo", "cls", "ltx", "sty", "TeX", "tex", NULL };
+static char TeX_help [] =
+"In LaTeX text, the argument of any of the commands `\\chapter',\n\
+`\\section', `\\subsection', `\\subsubsection', `\\eqno', `\\label',\n\
+`\\ref', `\\cite', `\\bibitem', `\\part', `\\appendix', `\\entry',\n\
+`\\index', `\\def', `\\newcommand', `\\renewcommand',\n\
+`\\newenvironment' or `\\renewenvironment' is a tag.\n\
+\n\
+Other commands can be specified by setting the environment variable\n\
+`TEXTAGS' to a colon-separated list like, for example,\n\
+     TEXTAGS=\"mycommand:myothercommand\".";
+
 
 static char *Texinfo_suffixes [] =
   { "texi", "texinfo", "txi", NULL };
+static char Texinfo_help [] =
+"for texinfo files, lines starting with @node are tagged.";
 
 static char *Yacc_suffixes [] =
   { "y", "y++", "ym", "yxx", "yy", NULL }; /* .ym is Objective yacc file */
+static char Yacc_help [] =
+"In Bison or Yacc input files, each rule defines as a tag the\n\
+nonterminal it constructs.  The portions of the file that contain\n\
+C code are parsed as C code (use --help --lang=c --lang=yacc\n\
+for full help).";
+
+static char auto_help [] =
+"`auto' is not a real language, it indicates to use\n\
+a default language for files base on file name suffix and file contents.";
+
+static char none_help [] =
+"`none' is not a real language, it indicates to only do\n\
+regexp processing on files.";
+
+static char no_lang_help [] =
+"No detailed help available for this language.";
+
 
 /*
  * Table of languages.
@@ -582,31 +754,33 @@ static char *Yacc_suffixes [] =
 
 static language lang_names [] =
 {
-  { "ada",               Ada_funcs,            NULL, Ada_suffixes,             NULL },
-  { "asm",               Asm_labels,           NULL, Asm_suffixes,             NULL },
-  { "c",                 default_C_entries,    NULL, default_C_suffixes,       NULL },
-  { "c++",               Cplusplus_entries,    NULL, Cplusplus_suffixes,       NULL },
-  { "c*",                Cstar_entries,        NULL, Cstar_suffixes,           NULL },
-  { "cobol",             Cobol_paragraphs,     NULL, Cobol_suffixes,           NULL },
-  { "erlang",            Erlang_functions,     NULL, Erlang_suffixes,          NULL },
-  { "fortran",           Fortran_functions,    NULL, Fortran_suffixes,         NULL },
-  { "java",              Cjava_entries,        NULL, Cjava_suffixes,           NULL },
-  { "lisp",              Lisp_functions,       NULL, Lisp_suffixes,            NULL },
-  { "makefile",   Makefile_targets,     Makefile_filenames, NULL,      NULL },
-  { "pascal",            Pascal_functions,     NULL, Pascal_suffixes,          NULL },
-  { "perl",              Perl_functions,     NULL, Perl_suffixes, Perl_interpreters },
-  { "php",       PHP_functions,        NULL, PHP_suffixes,             NULL },
-  { "postscript", Postscript_functions, NULL, Postscript_suffixes,     NULL },
-  { "proc",              plain_C_entries,      NULL, plain_C_suffixes,         NULL },
-  { "prolog",            Prolog_functions,     NULL, Prolog_suffixes,          NULL },
-  { "python",            Python_functions,     NULL, Python_suffixes,          NULL },
-  { "scheme",            Scheme_functions,     NULL, Scheme_suffixes,          NULL },
-  { "tex",               TeX_commands,         NULL, TeX_suffixes,             NULL },
-  { "texinfo",           Texinfo_nodes,        NULL, Texinfo_suffixes,         NULL },
-  { "yacc",              Yacc_entries,         NULL, Yacc_suffixes,            NULL },
-  { "auto", NULL },             /* default guessing scheme */
-  { "none", just_read_file },   /* regexp matching only */
-  { NULL, NULL }                /* end of list */
+  { "ada",       Ada_help,       Ada_funcs,         Ada_suffixes       },
+  { "asm",       Asm_help,       Asm_labels,        Asm_suffixes       },
+  { "c",         default_C_help, default_C_entries, default_C_suffixes },
+  { "c++",       Cplusplus_help, Cplusplus_entries, Cplusplus_suffixes },
+  { "c*",        no_lang_help,   Cstar_entries,     Cstar_suffixes     },
+  { "cobol",     Cobol_help,     Cobol_paragraphs,  Cobol_suffixes     },
+  { "erlang",    Erlang_help,    Erlang_functions,  Erlang_suffixes    },
+  { "fortran",   Fortran_help,   Fortran_functions, Fortran_suffixes   },
+  { "html",      HTML_help,      HTML_labels,       HTML_suffixes      },
+  { "java",      Cjava_help,     Cjava_entries,     Cjava_suffixes     },
+  { "lisp",      Lisp_help,      Lisp_functions,    Lisp_suffixes      },
+  { "makefile",  Makefile_help,Makefile_targets,NULL,Makefile_filenames},
+  { "objc",      Objc_help,      plain_C_entries,   Objc_suffixes      },
+  { "pascal",    Pascal_help,    Pascal_functions,  Pascal_suffixes    },
+  { "perl",Perl_help,Perl_functions,Perl_suffixes,NULL,Perl_interpreters},
+  { "php",       PHP_help,       PHP_functions,     PHP_suffixes       },
+  { "postscript",PS_help,        PS_functions,      PS_suffixes        },
+  { "proc",      no_lang_help,   plain_C_entries,   plain_C_suffixes   },
+  { "prolog",    Prolog_help,    Prolog_functions,  Prolog_suffixes    },
+  { "python",    Python_help,    Python_functions,  Python_suffixes    },
+  { "scheme",    Scheme_help,    Scheme_functions,  Scheme_suffixes    },
+  { "tex",       TeX_help,       TeX_commands,      TeX_suffixes       },
+  { "texinfo",   Texinfo_help,   Texinfo_nodes,     Texinfo_suffixes   },
+  { "yacc",      Yacc_help,Yacc_entries,Yacc_suffixes,NULL,NULL,TRUE},
+  { "auto",      auto_help },                      /* default guessing scheme */
+  { "none",      none_help,      just_read_file }, /* regexp matching only */
+  { NULL }                /* end of list */
 };
 
 \f
@@ -629,35 +803,54 @@ default file names and dot suffixes:");
          printf (" .%s", *ext);
       puts ("");
     }
-  puts ("Where `auto' means use default language for files based on file\n\
+  puts ("where `auto' means use default language for files based on file\n\
 name suffix, and `none' means only do regexp processing on files.\n\
 If no language is specified and no matching suffix is found,\n\
 the first line of the file is read for a sharp-bang (#!) sequence\n\
 followed by the name of an interpreter.  If no such sequence is found,\n\
 Fortran is tried first; if no tags are found, C is tried next.\n\
-When parsing any C file, a \"class\" keyword switches to C++.\n\
-Compressed files are supported using gzip and bzip2.");
+When parsing any C file, a \"class\" or \"template\" keyword\n\
+switches to C++.");
+  puts ("Compressed files are supported using gzip and bzip2.\n\
+\n\
+For detailed help on a given language use, for example,\n\
+etags --help --lang=ada.");
 }
 
 #ifndef EMACS_NAME
-# define EMACS_NAME "GNU Emacs"
+# define EMACS_NAME "standalone"
 #endif
 #ifndef VERSION
-# define VERSION "21"
+# define VERSION "version"
 #endif
 static void
 print_version ()
 {
   printf ("%s (%s %s)\n", (CTAGS) ? "ctags" : "etags", EMACS_NAME, VERSION);
-  puts ("Copyright (C) 1999 Free Software Foundation, Inc. and Ken Arnold");
+  puts ("Copyright (C) 2002 Free Software Foundation, Inc. and Ken Arnold");
   puts ("This program is distributed under the same terms as Emacs");
 
   exit (GOOD);
 }
 
 static void
-print_help ()
+print_help (argbuffer)
+     argument *argbuffer;
 {
+  bool help_for_lang = FALSE;
+
+  for (; argbuffer->arg_type != at_end; argbuffer++)
+    if (argbuffer->arg_type == at_language)
+      {
+       if (help_for_lang)
+         puts ("");
+       puts (argbuffer->lang->help);
+       help_for_lang = TRUE;
+      }
+
+  if (help_for_lang)
+    exit (GOOD);
+
   printf ("Usage: %s [options] [[regex-option ...] file-name] ...\n\
 \n\
 These are the options accepted by %s.\n", progname, progname);
@@ -667,17 +860,16 @@ These are the options accepted by %s.\n", progname, progname);
   puts ("Long option names do not work with this executable, as it is not\n\
 linked with GNU getopt.");
 #endif /* LONG_OPTIONS */
-  puts ("A - as file name means read names from stdin (one per line).");
-  if (!CTAGS)
-    printf ("  Absolute names are stored in the output file as they are.\n\
-Relative ones are stored relative to the output file's directory.");
-  puts ("\n");
+  puts ("  A - as file name means read names from stdin (one per line).\n\
+Absolute names are stored in the output file as they are.\n\
+Relative ones are stored relative to the output file's directory.\n");
 
-  puts ("-a, --append\n\
+  if (!CTAGS)
+    puts ("-a, --append\n\
         Append tag entries to existing tags file.");
 
   puts ("--packages-only\n\
-        For Ada files, only generate tags for packages .");
+        For Ada files, only generate tags for packages.");
 
   if (CTAGS)
     puts ("-B, --backward-search\n\
@@ -687,7 +879,7 @@ Relative ones are stored relative to the output file's directory.");
   /* This option is mostly obsolete, because etags can now automatically
      detect C++.  Retained for backward compatibility and for debugging and
      experimentation.  In principle, we could want to tag as C++ even
-     before any "class" keyword.
+     before any "class" or "template" keyword.
   puts ("-C, --c++\n\
         Treat files whose name suffix defaults to C language as C++ files.");
   */
@@ -709,15 +901,14 @@ Relative ones are stored relative to the output file's directory.");
        This makes the tags file smaller.");
 
   if (!CTAGS)
-    {
-      puts ("-i FILE, --include=FILE\n\
+    puts ("-i FILE, --include=FILE\n\
         Include a note in tag file indicating that, when searching for\n\
         a tag, one should also consult the tags file FILE after\n\
         checking the current file.");
-      puts ("-l LANG, --language=LANG\n\
+
+  puts ("-l LANG, --language=LANG\n\
         Force the following files to be considered as written in the\n\
        named language up to the next --language=LANG option.");
-    }
 
   if (CTAGS)
     puts ("--globals\n\
@@ -727,29 +918,31 @@ Relative ones are stored relative to the output file's directory.");
        Do not create tag entries for global variables in some\n\
        languages.  This makes the tags file smaller.");
   puts ("--members\n\
-       Create tag entries for member variables in C and derived languages.");
+       Create tag entries for members of structures in some languages.");
 
 #ifdef ETAGS_REGEXPS
-  puts ("-r /REGEXP/, --regex=/REGEXP/ or --regex=@regexfile\n\
-        Make a tag for each line matching pattern REGEXP in the following\n\
-       files.  {LANGUAGE}/REGEXP/ uses REGEXP for LANGUAGE files only.\n\
-       regexfile is a file containing one REGEXP per line.\n\
-       REGEXP is anchored (as if preceded by ^).\n\
-       The form /REGEXP/NAME/ creates a named tag.\n\
+  puts ("-r REGEXP, --regex=REGEXP or --regex=@regexfile\n\
+        Make a tag for each line matching a regular expression pattern\n\
+       in the following files.  {LANGUAGE}REGEXP uses REGEXP for LANGUAGE\n\
+       files only.  REGEXFILE is a file containing one REGEXP per line.\n\
+       REGEXP takes the form /TAGREGEXP/TAGNAME/MODS, where TAGNAME/ is\n\
+       optional.  The TAGREGEXP pattern is anchored (as if preceded by ^).");
+  puts ("      If TAGNAME/ is present, the tags created are named.\n\
        For example Tcl named tags can be created with:\n\
-       --regex=\"/proc[ \\t]+\\([^ \\t]+\\)/\\1/.\"");
-  puts ("-c /REGEXP/, --ignore-case-regex=/REGEXP/ or --ignore-case-regex=@regexfile\n\
-        Like -r, --regex but ignore case when matching expressions.");
+         --regex=\"/proc[ \\t]+\\([^ \\t]+\\)/\\1/.\".\n\
+       MODS are optional one-letter modifiers: `i' means to ignore case,\n\
+       `m' means to allow multi-line matches, `s' implies `m' and\n\
+       causes dot to match any character, including newline.");
   puts ("-R, --no-regex\n\
         Don't create tags from regexps for the following files.");
 #endif /* ETAGS_REGEXPS */
+  puts ("-I, --ignore-indentation\n\
+        In C and C++ do not assume that a closing brace in the first\n\
+        column is the final brace of a function or structure definition.");
   puts ("-o FILE, --output=FILE\n\
         Write the tags to FILE.");
-  puts ("-I, --ignore-indentation\n\
-        Don't rely on indentation quite as much as normal.  Currently,\n\
-        this means not to assume that a closing brace in the first\n\
-        column is the final brace of a function or structure\n\
-        definition in C and C++.");
+  puts ("--parse-stdin=NAME\n\
+        Read from standard input and record tags as belonging to file NAME.");
 
   if (CTAGS)
     {
@@ -758,13 +951,19 @@ Relative ones are stored relative to the output file's directory.");
       puts ("-T, --typedefs-and-c++\n\
         Generate tag entries for C typedefs, C struct/enum/union tags,\n\
         and C++ member functions.");
-      puts ("-u, --update\n\
+    }
+
+  if (CTAGS)
+    puts ("-u, --update\n\
         Update the tag entries for the given files, leaving tag\n\
         entries for other files in place.  Currently, this is\n\
         implemented by deleting the existing entries for the given\n\
         files and then rewriting the new entries at the end of the\n\
         tags file.  It is often faster to simply rebuild the entire\n\
         tag file than to use this.");
+
+  if (CTAGS)
+    {
       puts ("-v, --vgrind\n\
         Generates an index of items intended for human consumption,\n\
         similar to the output of vgrind.  The index is sorted, and\n\
@@ -782,7 +981,9 @@ Relative ones are stored relative to the output file's directory.");
   puts ("-V, --version\n\
         Print the version of the program.\n\
 -h, --help\n\
-        Print this help message.");
+        Print this help message.\n\
+        Followed by one or more `--language' options prints detailed\n\
+        help about tag generation for the specified languages.");
 
   print_language_names ();
 
@@ -793,22 +994,6 @@ Relative ones are stored relative to the output file's directory.");
 }
 
 \f
-enum argument_type
-{
-  at_language,
-  at_regexp,
-  at_filename,
-  at_icregexp
-};
-
-/* This structure helps us allow mixing of --lang and file names. */
-typedef struct
-{
-  enum argument_type arg_type;
-  char *what;
-  language *lang;              /* language of the regexp */
-} argument;
-
 #ifdef VMS                     /* VMS specific functions */
 
 #define        EOS     '\0'
@@ -942,13 +1127,16 @@ main (argc, argv)
   int i;
   unsigned int nincluded_files;
   char **included_files;
-  char *this_file;
   argument *argbuffer;
   int current_arg, file_count;
   linebuffer filename_lb;
+  bool help_asked = FALSE;
 #ifdef VMS
   bool got_err;
 #endif
+ char *optstring;
+ int opt;
+
 
 #ifdef DOS_NT
   _fmode = O_BINARY;   /* all of files are treated as binary files */
@@ -964,14 +1152,6 @@ main (argc, argv)
      is small. */
   argbuffer = xnew (argc, argument);
 
-#ifdef ETAGS_REGEXPS
-  /* Set syntax for regular expression routines. */
-  re_set_syntax (RE_SYNTAX_EMACS | RE_INTERVALS);
-  /* Translation table for case-insensitive search. */
-  for (i = 0; i < CHAR_SET_SIZE; i++)
-    lc_trans[i] = lowcase (i);
-#endif /* ETAGS_REGEXPS */
-
   /*
    * If etags, always find typedefs and structure tags.  Why not?
    * Also default to find macro constants, enum constants and
@@ -981,117 +1161,115 @@ main (argc, argv)
     {
       typedefs = typedefs_or_cplusplus = constantypedefs = TRUE;
       globals = TRUE;
-      declarations = FALSE;
-      members = FALSE;
     }
 
-  while (1)
-    {
-      int opt;
-      char *optstring;
-
+  optstring = "-";
 #ifdef ETAGS_REGEXPS
-      optstring = "-aCdDf:Il:o:r:c:RStTi:BuvxwVhH";
-#else
-      optstring = "-aCdDf:Il:o:StTi:BuvxwVhH";
+  optstring = "-r:Rc:";
 #endif /* ETAGS_REGEXPS */
-
 #ifndef LONG_OPTIONS
-      optstring = optstring + 1;
+  optstring = optstring + 1;
 #endif /* LONG_OPTIONS */
+  optstring = concat (optstring,
+                     "Cf:Il:o:SVhH",
+                     (CTAGS) ? "BxdtTuvw" : "aDi:");
 
-      opt = getopt_long (argc, argv, optstring, longopts, 0);
-      if (opt == EOF)
+  while ((opt = getopt_long (argc, argv, optstring, longopts, 0)) != EOF)
+    switch (opt)
+      {
+      case 0:
+       /* If getopt returns 0, then it has already processed a
+          long-named option.  We should do nothing.  */
        break;
 
-      switch (opt)
-       {
-       case 0:
-         /* If getopt returns 0, then it has already processed a
-            long-named option.  We should do nothing.  */
-         break;
+      case 1:
+       /* This means that a file name has been seen.  Record it. */
+       argbuffer[current_arg].arg_type = at_filename;
+       argbuffer[current_arg].what     = optarg;
+       ++current_arg;
+       ++file_count;
+       break;
 
-       case 1:
-         /* This means that a file name has been seen.  Record it. */
-         argbuffer[current_arg].arg_type = at_filename;
-         argbuffer[current_arg].what = optarg;
-         ++current_arg;
-         ++file_count;
-         break;
+      case STDIN:
+       /* Parse standard input.  Idea by Vivek <vivek@etla.org>. */
+       argbuffer[current_arg].arg_type = at_stdin;
+       argbuffer[current_arg].what     = optarg;
+       ++current_arg;
+       ++file_count;
+       if (parsing_stdin)
+         fatal ("cannot parse standard input more than once", (char *)NULL);
+       parsing_stdin = TRUE;
+       break;
 
-         /* Common options. */
-       case 'a': append_to_tagfile = TRUE;     break;
-       case 'C': cplusplus = TRUE;             break;
-       case 'd': constantypedefs = TRUE;       break;
-       case 'D': constantypedefs = FALSE;      break;
-       case 'f':               /* for compatibility with old makefiles */
-       case 'o':
-         if (tagfile)
-           {
-             error ("-o option may only be given once.", (char *)NULL);
-             suggest_asking_for_help ();
-           }
-         tagfile = optarg;
-         break;
-       case 'I':
-       case 'S':               /* for backward compatibility */
-         noindentypedefs = TRUE;
-         break;
-       case 'l':
+       /* Common options. */
+      case 'C': cplusplus = TRUE;              break;
+      case 'f':                /* for compatibility with old makefiles */
+      case 'o':
+       if (tagfile)
          {
-           language *lang = get_language_from_langname (optarg);
-           if (lang != NULL)
-             {
-               argbuffer[current_arg].lang = lang;
-               argbuffer[current_arg].arg_type = at_language;
-               ++current_arg;
-             }
+           error ("-o option may only be given once.", (char *)NULL);
+           suggest_asking_for_help ();
+           /* NOTREACHED */
          }
-         break;
-       case 'r':
-         argbuffer[current_arg].arg_type = at_regexp;
-         argbuffer[current_arg].what = optarg;
-         ++current_arg;
-         break;
-       case 'R':
-         argbuffer[current_arg].arg_type = at_regexp;
-         argbuffer[current_arg].what = NULL;
-         ++current_arg;
-         break;
-        case 'c':
-         argbuffer[current_arg].arg_type = at_icregexp;
-         argbuffer[current_arg].what = optarg;
-         ++current_arg;
-         break;
-       case 'V':
-         print_version ();
-         break;
-       case 'h':
-       case 'H':
-         print_help ();
-         break;
-       case 't':
-         typedefs = TRUE;
-         break;
-       case 'T':
-         typedefs = typedefs_or_cplusplus = TRUE;
-         break;
-         /* Etags options */
-       case 'i':
-         included_files[nincluded_files++] = optarg;
-         break;
-         /* 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;
-       default:
-         suggest_asking_for_help ();
+       tagfile = optarg;
+       break;
+      case 'I':
+      case 'S':                /* for backward compatibility */
+       ignoreindent = TRUE;
+       break;
+      case 'l':
+       {
+         language *lang = get_language_from_langname (optarg);
+         if (lang != NULL)
+           {
+             argbuffer[current_arg].lang = lang;
+             argbuffer[current_arg].arg_type = at_language;
+             ++current_arg;
+           }
        }
-    }
+       break;
+      case 'c':
+       /* Backward compatibility: support obsolete --ignore-case-regexp. */
+       optarg = concat (optarg, "i", ""); /* memory leak here */
+       /* FALLTHRU */
+      case 'r':
+       argbuffer[current_arg].arg_type = at_regexp;
+       argbuffer[current_arg].what = optarg;
+       ++current_arg;
+       break;
+      case 'R':
+       argbuffer[current_arg].arg_type = at_regexp;
+       argbuffer[current_arg].what = NULL;
+       ++current_arg;
+       break;
+      case 'V':
+       print_version ();
+       break;
+      case 'h':
+      case 'H':
+       help_asked = TRUE;
+       break;
 
-  for (; optind < argc; ++optind)
+       /* Etags options */
+      case 'a': append_to_tagfile = TRUE;                      break;
+      case 'D': constantypedefs = FALSE;                       break;
+      case 'i': included_files[nincluded_files++] = optarg;    break;
+
+       /* Ctags options. */
+      case 'B': searchar = '?';                                        break;
+      case 'd': constantypedefs = TRUE;                                break;
+      case 't': typedefs = TRUE;                               break;
+      case 'T': typedefs = typedefs_or_cplusplus = TRUE;       break;
+      case 'u': update = TRUE;                                 break;
+      case 'v': vgrind_style = TRUE;                     /*FALLTHRU*/
+      case 'x': cxref_style = TRUE;                            break;
+      case 'w': no_warnings = TRUE;                            break;
+      default:
+       suggest_asking_for_help ();
+       /* NOTREACHED */
+      }
+
+  for (; optind < argc; optind++)
     {
       argbuffer[current_arg].arg_type = at_filename;
       argbuffer[current_arg].what = argv[optind];
@@ -1099,10 +1277,17 @@ main (argc, argv)
       ++file_count;
     }
 
+  argbuffer[current_arg].arg_type = at_end;
+
+  if (help_asked)
+    print_help (argbuffer);
+    /* NOTREACHED */
+
   if (nincluded_files == 0 && file_count == 0)
     {
       error ("no input files specified.", (char *)NULL);
       suggest_asking_for_help ();
+      /* NOTREACHED */
     }
 
   if (tagfile == NULL)
@@ -1121,8 +1306,10 @@ main (argc, argv)
 
   init ();                     /* set up boolean "functions" */
 
-  initbuffer (&lb);
-  initbuffer (&filename_lb);
+  linebuffer_init (&lb);
+  linebuffer_init (&filename_lb);
+  linebuffer_init (&filebuf);
+  linebuffer_init (&token_name);
 
   if (!CTAGS)
     {
@@ -1145,19 +1332,19 @@ main (argc, argv)
   /*
    * Loop through files finding functions.
    */
-  for (i = 0; i < current_arg; ++i)
+  for (i = 0; i < current_arg; i++)
     {
+      static language *lang;   /* non-NULL if language is forced */
+      char *this_file;
+
       switch (argbuffer[i].arg_type)
        {
        case at_language:
-         forced_lang = argbuffer[i].lang;
+         lang = argbuffer[i].lang;
          break;
 #ifdef ETAGS_REGEXPS
        case at_regexp:
-         analyse_regex (argbuffer[i].what, FALSE);
-         break;
-       case at_icregexp:
-         analyse_regex (argbuffer[i].what, TRUE);
+         analyse_regex (argbuffer[i].what);
          break;
 #endif
        case at_filename:
@@ -1179,29 +1366,50 @@ main (argc, argv)
              /* Input file named "-" means read file names from stdin
                 (one per line) and use them. */
              if (streq (this_file, "-"))
-               while (readline_internal (&filename_lb, stdin) > 0)
-                 process_file (filename_lb.buffer);
+               {
+                 if (parsing_stdin)
+                   fatal ("cannot parse standard input AND read file names from it",
+                          (char *)NULL);
+                 while (readline_internal (&filename_lb, stdin) > 0)
+                   process_file_name (filename_lb.buffer, lang);
+               }
              else
-               process_file (this_file);
+               process_file_name (this_file, lang);
 #ifdef VMS
            }
 #endif
          break;
+        case at_stdin:
+          this_file = argbuffer[i].what;
+          process_file (stdin, this_file, lang);
+          break;
        }
     }
 
 #ifdef ETAGS_REGEXPS
-  free_patterns ();
+  free_regexps ();
 #endif /* ETAGS_REGEXPS */
+  free (lb.buffer);
+  free (filebuf.buffer);
+  free (token_name.buffer);
 
   if (!CTAGS || cxref_style)
     {
-      put_entries (head);
-      free_tree (head);
-      head = NULL;
+      put_entries (nodehead);  /* write the remainig tags (ETAGS) */
+      free_tree (nodehead);
+      nodehead = NULL;
       if (!CTAGS)
-       while (nincluded_files-- > 0)
-         fprintf (tagf, "\f\n%s,include\n", *included_files++);
+       {
+         fdesc *fdp;
+
+         /* Output file entries that have no tags. */
+         for (fdp = fdhead; fdp != NULL; fdp = fdp->next)
+           if (!fdp->written)
+             fprintf (tagf, "\f\n%s,0\n", fdp->taggedfname);
+
+         while (nincluded_files-- > 0)
+           fprintf (tagf, "\f\n%s,include\n", *included_files++);
+       }
 
       if (fclose (tagf) == EOF)
        pfatal (tagfile);
@@ -1213,8 +1421,14 @@ main (argc, argv)
       char cmd[BUFSIZ];
       for (i = 0; i < current_arg; ++i)
        {
-         if (argbuffer[i].arg_type != at_filename)
-           continue;
+         switch (argbuffer[i].arg_type)
+           {
+           case at_filename:
+           case at_stdin:
+             break;
+           default:
+             continue;         /* the for loop */
+           }
          sprintf (cmd,
                   "mv %s OTAGS;fgrep -v '\t%s\t' OTAGS >%s;rm OTAGS",
                   tagfile, argbuffer[i].what, tagfile);
@@ -1227,23 +1441,22 @@ main (argc, argv)
   tagf = fopen (tagfile, append_to_tagfile ? "a" : "w");
   if (tagf == NULL)
     pfatal (tagfile);
-  put_entries (head);
-  free_tree (head);
-  head = NULL;
+  put_entries (nodehead);      /* write all the tags (CTAGS) */
+  free_tree (nodehead);
+  nodehead = NULL;
   if (fclose (tagf) == EOF)
     pfatal (tagfile);
 
   if (update)
     {
-      char cmd[BUFSIZ];
-      sprintf (cmd, "sort %s -o %s", tagfile, tagfile);
+      char cmd[2*BUFSIZ+10];
+      sprintf (cmd, "sort -o %.*s %.*s", BUFSIZ, tagfile, BUFSIZ, tagfile);
       exit (system (cmd));
     }
   return GOOD;
 }
 
 
-
 /*
  * Return a compressor given the file name.  If EXTPTR is non-zero,
  * return a pointer into FILE where the compressor-specific
@@ -1336,8 +1549,9 @@ get_language_from_interpreter (interpreter)
  * Return a language given the file name.
  */
 static language *
-get_language_from_filename (file)
+get_language_from_filename (file, case_sensitive)
      char *file;
+     bool case_sensitive;
 {
   language *lang;
   char **name, **ext, *suffix;
@@ -1346,7 +1560,9 @@ get_language_from_filename (file)
   for (lang = lang_names; lang->name != NULL; lang++)
     if (lang->filenames != NULL)
       for (name = lang->filenames; *name != NULL; name++)
-       if (streq (*name, file))
+       if ((case_sensitive)
+           ? streq (*name, file)
+           : strcaseeq (*name, file))
          return lang;
 
   /* If not found, try suffix after last dot. */
@@ -1357,28 +1573,30 @@ get_language_from_filename (file)
   for (lang = lang_names; lang->name != NULL; lang++)
     if (lang->suffixes != NULL)
       for (ext = lang->suffixes; *ext != NULL; ext++)
-       if (streq (*ext, suffix))
+       if ((case_sensitive)
+           ? streq (*ext, suffix)
+           : strcaseeq (*ext, suffix))
          return lang;
   return NULL;
 }
 
-
-
+\f
 /*
  * This routine is called on each file argument.
  */
 static void
-process_file (file)
+process_file_name (file, lang)
      char *file;
+     language *lang;
 {
   struct stat stat_buf;
   FILE *inf;
+  fdesc *fdp;
   compressor *compr;
   char *compressed_name, *uncompressed_name;
   char *ext, *real_name;
   int retval;
 
-
   canonicalize_filename (file);
   if (streq (file, tagfile) && !streq (tagfile, "-"))
     {
@@ -1396,25 +1614,14 @@ process_file (file)
       uncompressed_name = savenstr (file, ext - file);
     }
 
-  /* If the canonicalized uncompressed name has already be dealt with,
-     skip it silently, else add it to the list. */
-  {
-    typedef struct processed_file
+  /* If the canonicalized uncompressed name
+     has already been dealt with, skip it silently. */
+  for (fdp = fdhead; fdp != NULL; fdp = fdp->next)
     {
-      char *filename;
-      struct processed_file *next;
-    } processed_file;
-    static processed_file *pf_head = NULL;
-    register processed_file *fnp;
-
-    for (fnp = pf_head; fnp != NULL; fnp = fnp->next)
-      if (streq (uncompressed_name, fnp->filename))
-       goto exit;
-    fnp = pf_head;
-    pf_head = xnew (1, struct processed_file);
-    pf_head->filename = savestr (uncompressed_name);
-    pf_head->next = fnp;
-  }
+      assert (fdp->infname != NULL);
+      if (streq (uncompressed_name, fdp->infname))
+       goto cleanup;
+    }
 
   if (stat (real_name, &stat_buf) != 0)
     {
@@ -1461,14 +1668,14 @@ process_file (file)
       if (real_name == NULL)
        {
          perror (file);
-         goto exit;
+         goto cleanup;
        }
     } /* try with a different name */
 
   if (!S_ISREG (stat_buf.st_mode))
     {
       error ("skipping %s: it is not a regular file.", real_name);
-      goto exit;
+      goto cleanup;
     }
   if (real_name == compressed_name)
     {
@@ -1481,26 +1688,11 @@ process_file (file)
   if (inf == NULL)
     {
       perror (real_name);
-      goto exit;
+      goto cleanup;
     }
 
-  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
-    {
-      /* 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);
+  process_file (inf, uncompressed_name, lang);
 
-  free (curfiledir);
   if (real_name == compressed_name)
     retval = pclose (inf);
   else
@@ -1508,12 +1700,86 @@ process_file (file)
   if (retval < 0)
     pfatal (file);
 
- exit:
-  if (compressed_name) free(compressed_name);
-  if (uncompressed_name) free(uncompressed_name);
+ cleanup:
+  if (compressed_name) free (compressed_name);
+  if (uncompressed_name) free (uncompressed_name);
+  last_node = NULL;
+  curfdp = NULL;
   return;
 }
 
+static void
+process_file (fh, fn, lang)
+     FILE *fh;
+     char *fn;
+     language *lang;
+{
+  static const fdesc emptyfdesc;
+  fdesc *fdp;
+
+  /* Create a new input file description entry. */
+  fdp = xnew (1, fdesc);
+  *fdp = emptyfdesc;
+  fdp->next = fdhead;
+  fdp->infname = savestr (fn);
+  fdp->lang = lang;
+  fdp->infabsname = absolute_filename (fn, cwd);
+  fdp->infabsdir = absolute_dirname (fn, cwd);
+  if (filename_is_absolute (fn))
+    {
+      /* An absolute file name.  Canonicalize it. */
+      fdp->taggedfname = absolute_filename (fn, NULL);
+    }
+  else
+    {
+      /* A file name relative to cwd.  Make it relative
+        to the directory of the tags file. */
+      fdp->taggedfname = relative_filename (fn, tagfiledir);
+    }
+  fdp->usecharno = TRUE;       /* use char position when making tags */
+  fdp->prop = NULL;
+  fdp->written = FALSE;                /* not written on tags file yet */
+
+  fdhead = fdp;
+  curfdp = fdhead;             /* the current file description */
+
+  find_entries (fh);
+
+  /* If not Ctags, and if this is not metasource and if it contained no #line
+     directives, we can write the tags and free all nodes pointing to
+     curfdp. */
+  if (!CTAGS
+      && curfdp->usecharno     /* no #line directives in this file */
+      && !curfdp->lang->metasource)
+    {
+      node *np, *prev;
+
+      /* Look for the head of the sublist relative to this file.  See add_node
+        for the structure of the node tree. */
+      prev = NULL;
+      for (np = nodehead; np != NULL; prev = np, np = np->left)
+       if (np->fdp == curfdp)
+         break;
+
+      /* If we generated tags for this file, write and delete them. */
+      if (np != NULL)
+       {
+         /* This is the head of the last sublist, if any.  The following
+            instructions depend on this being true. */
+         assert (np->left == NULL);
+
+         assert (fdhead == curfdp);
+         assert (last_node->fdp == curfdp);
+         put_entries (np);     /* write tags for file curfdp->taggedfname */
+         free_tree (np);       /* remove the written nodes */
+         if (prev == NULL)
+           nodehead = NULL;    /* no nodes left */
+         else
+           prev->left = NULL;  /* delete the pointer to the sublist */
+       }
+    }
+}
+
 /*
  * This routine sets up the boolean pseudo-functions which work
  * by setting boolean flags dependent upon the corresponding character.
@@ -1545,37 +1811,34 @@ init ()
  * This routine opens the specified file and calls the function
  * which finds the function and type definitions.
  */
-static node *last_node = NULL;
-
 static void
-find_entries (file, inf)
-     char *file;
+find_entries (inf)
      FILE *inf;
 {
   char *cp;
-  language *lang;
-  node *old_last_node;
+  language *lang = curfdp->lang;
+  Lang_function *parser = NULL;
 
   /* If user specified a language, use it. */
-  lang = forced_lang;
   if (lang != NULL && lang->function != NULL)
     {
-      curlang = lang;
-      lang->function (inf);
-      return;
+      parser = lang->function;
     }
 
-  /* Try to guess the language given the file name. */
-  lang = get_language_from_filename (file);
-  if (lang != NULL && lang->function != NULL)
+  /* Else try to guess the language given the file name. */
+  if (parser == NULL)
     {
-      curlang = lang;
-      lang->function (inf);
-      return;
+      lang = get_language_from_filename (curfdp->infname, TRUE);
+      if (lang != NULL && lang->function != NULL)
+       {
+         curfdp->lang = lang;
+         parser = lang->function;
+       }
     }
 
-  /* Look for sharp-bang as the first two characters. */
-  if (readline_internal (&lb, inf) > 0
+  /* Else look for sharp-bang as the first two characters. */
+  if (parser == NULL
+      && readline_internal (&lb, inf) > 0
       && lb.len >= 2
       && lb.buffer[0] == '#'
       && lb.buffer[1] == '!')
@@ -1598,144 +1861,214 @@ find_entries (file, inf)
          lang = get_language_from_interpreter (lp);
          if (lang != NULL && lang->function != NULL)
            {
-             curlang = lang;
-             lang->function (inf);
-             return;
+             curfdp->lang = lang;
+             parser = lang->function;
            }
        }
     }
+
   /* We rewind here, even if inf may be a pipe.  We fail if the
      length of the first line is longer than the pipe block size,
      which is unlikely. */
   rewind (inf);
 
-  /* Try Fortran. */
-  old_last_node = last_node;
-  curlang = get_language_from_langname ("fortran");
-  Fortran_functions (inf);
+  /* Else try to guess the language given the case insensitive file name. */
+  if (parser == NULL)
+    {
+      lang = get_language_from_filename (curfdp->infname, FALSE);
+      if (lang != NULL && lang->function != NULL)
+       {
+         curfdp->lang = lang;
+         parser = lang->function;
+       }
+    }
 
-  /* No Fortran entries found.  Try C. */
-  if (old_last_node == last_node)
+  /* Else try Fortran or C. */
+  if (parser == NULL)
     {
-      /* We do not tag if rewind fails.
-        Only the file name will be recorded in the tags file. */
-      rewind (inf);
-      curlang = get_language_from_langname (cplusplus ? "c++" : "c");
-      default_C_entries (inf);
+      node *old_last_node = last_node;
+
+      curfdp->lang = get_language_from_langname ("fortran");
+      find_entries (inf);
+
+      if (old_last_node == last_node)
+       /* No Fortran entries found.  Try C. */
+       {
+         /* We do not tag if rewind fails.
+            Only the file name will be recorded in the tags file. */
+         rewind (inf);
+         curfdp->lang = get_language_from_langname (cplusplus ? "c++" : "c");
+         find_entries (inf);
+       }
+      return;
     }
-  return;
+
+  if (!no_line_directive
+      && curfdp->lang != NULL && curfdp->lang->metasource)
+    /* It may be that this is a bingo.y file, and we already parsed a bingo.c
+       file, or anyway we parsed a file that is automatically generated from
+       this one.  If this is the case, the bingo.c file contained #line
+       directives that generated tags pointing to this file.  Let's delete
+       them all before parsing this file, which is the real source. */
+    {
+      fdesc **fdpp = &fdhead;
+      while (*fdpp != NULL)
+       if (*fdpp != curfdp
+           && streq ((*fdpp)->taggedfname, curfdp->taggedfname))
+         /* We found one of those!  We must delete both the file description
+            and all tags referring to it. */
+         {
+           fdesc *badfdp = *fdpp;
+
+           /* Delete the tags referring to badfdp->taggedfname
+              that were obtained from badfdp->infname. */
+           invalidate_nodes (badfdp, &nodehead);
+
+           *fdpp = badfdp->next; /* remove the bad description from the list */
+           free_fdesc (badfdp);
+         }
+       else
+         fdpp = &(*fdpp)->next; /* advance the list pointer */
+    }
+
+  assert (parser != NULL);
+
+  /* Generic initialisations before reading from file. */
+  linebuffer_setlen (&filebuf, 0); /* reset the file buffer */
+
+  /* Generic initialisations before parsing file with readline. */
+  lineno = 0;                 /* reset global line number */
+  charno = 0;                 /* reset global char number */
+  linecharno = 0;             /* reset global char number of line start */
+
+  parser (inf);
+
+#ifdef ETAGS_REGEXPS
+  regex_tag_multiline ();
+#endif /* ETAGS_REGEXPS */
 }
 
 \f
-/* Record a tag. */
+/*
+ * Check whether an implicitly named tag should be created,
+ * then call `pfnote'.
+ * NAME is a string that is internally copied by this function.
+ *
+ * TAGS format specification
+ * Idea by Sam Kendall <kendall@mv.mv.com> (1997)
+ * The following is explained in some more detail in etc/ETAGS.EBNF.
+ *
+ * make_tag creates tags with "implicit tag names" (unnamed tags)
+ * if the following are all true, assuming NONAM=" \f\t\n\r()=,;":
+ *  1. NAME does not contain any of the characters in NONAM;
+ *  2. LINESTART contains name as either a rightmost, or rightmost but
+ *     one character, substring;
+ *  3. the character, if any, immediately before NAME in LINESTART must
+ *     be a character in NONAM;
+ *  4. the character, if any, immediately after NAME in LINESTART must
+ *     also be a character in NONAM.
+ *
+ * The implementation uses the notinname() macro, which recognises the
+ * characters stored in the string `nonam'.
+ * etags.el needs to use the same characters that are in NONAM.
+ */
 static void
-pfnote (name, is_func, linestart, linelen, lno, cno)
+make_tag (name, namelen, is_func, linestart, linelen, lno, cno)
      char *name;               /* tag name, or NULL if unnamed */
+     int namelen;              /* tag length */
      bool is_func;             /* tag is a function */
      char *linestart;          /* start of the line where tag is */
      int linelen;              /* length of the line where tag is */
      int lno;                  /* line number */
      long cno;                 /* character number */
 {
-  register node *np;
-
-  if (CTAGS && name == NULL)
-    return;
-
-  np = xnew (1, node);
+  bool named = (name != NULL && namelen > 0);
 
-  /* If ctags mode, change name "main" to M<thisfilename>. */
-  if (CTAGS && !cxref_style && streq (name, "main"))
+  if (!CTAGS && named)         /* maybe set named to false */
+    /* Let's try to make an implicit tag name, that is, create an unnamed tag
+       such that etags.el can guess a name from it. */
     {
-      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';
+      int i;
+      register char *cp = name;
+
+      for (i = 0; i < namelen; i++)
+       if (notinname (*cp++))
+         break;
+      if (i == namelen)                                /* rule #1 */
+       {
+         cp = linestart + linelen - namelen;
+         if (notinname (linestart[linelen-1]))
+           cp -= 1;                            /* rule #4 */
+         if (cp >= linestart                   /* rule #2 */
+             && (cp == linestart
+                 || notinname (cp[-1]))        /* rule #3 */
+             && strneq (name, cp, namelen))    /* rule #2 */
+           named = FALSE;      /* use implicit tag name */
+       }
+    }
+
+  if (named)
+    name = savenstr (name, namelen);
+  else
+    name = NULL;
+  pfnote (name, is_func, linestart, linelen, lno, cno);
+}
+
+/* Record a tag. */
+static void
+pfnote (name, is_func, linestart, linelen, lno, cno)
+     char *name;               /* tag name, or NULL if unnamed */
+     bool is_func;             /* tag is a function */
+     char *linestart;          /* start of the line where tag is */
+     int linelen;              /* length of the line where tag is */
+     int lno;                  /* line number */
+     long cno;                 /* character number */
+{
+  register node *np;
+
+  assert (name == NULL || name[0] != '\0');
+  if (CTAGS && name == NULL)
+    return;
+
+  np = xnew (1, node);
+
+  /* If ctags mode, change name "main" to M<thisfilename>. */
+  if (CTAGS && !cxref_style && streq (name, "main"))
+    {
+      register char *fp = etags_strrchr (curfdp->taggedfname, '/');
+      np->name = concat ("M", fp == NULL ? curfdp->taggedfname : fp + 1, "");
+      fp = etags_strrchr (np->name, '.');
+      if (fp != NULL && fp[1] != '\0' && fp[2] == '\0')
+       fp[0] = '\0';
     }
   else
     np->name = name;
+  np->valid = TRUE;
   np->been_warned = FALSE;
-  np->file = curtagfname;
+  np->fdp = curfdp;
   np->is_func = is_func;
   np->lno = lno;
-  if (nocharno)
-    np->cno = invalidcharno;
-  else
+  if (np->fdp->usecharno)
     /* 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 */ ;
+  else
+    np->cno = invalidcharno;
   np->left = np->right = NULL;
   if (CTAGS && !cxref_style)
     {
       if (strlen (linestart) < 50)
-       np->pat = concat (linestart, "$", "");
+       np->regex = concat (linestart, "$", "");
       else
-       np->pat = savenstr (linestart, 50);
+       np->regex = savenstr (linestart, 50);
     }
   else
-    np->pat = savenstr (linestart, linelen);
+    np->regex = savenstr (linestart, linelen);
 
-  add_node (np, &head);
-}
-
-/*
- * TAGS format specification
- * Idea by Sam Kendall <kendall@mv.mv.com> (1997)
- *
- * pfnote should emit the optimized form [unnamed tag] only if:
- *  1. name does not contain any of the characters " \t\r\n(),;";
- *  2. linestart contains name as either a rightmost, or rightmost but
- *     one character, substring;
- *  3. the character, if any, immediately before name in linestart must
- *     be one of the characters " \t(),;";
- *  4. the character, if any, immediately after name in linestart must
- *     also be one of the characters " \t(),;".
- *
- * The real implementation uses the notinname() macro, which recognises
- * characters slightly different from " \t\r\n(),;".  See the variable
- * `nonam'.
- */
-#define traditional_tag_style TRUE
-static void
-new_pfnote (name, namelen, is_func, linestart, linelen, lno, cno)
-     char *name;               /* tag name, or NULL if unnamed */
-     int namelen;              /* tag length */
-     bool is_func;             /* tag is a function */
-     char *linestart;          /* start of the line where tag is */
-     int linelen;              /* length of the line where tag is */
-     int lno;                  /* line number */
-     long cno;                 /* character number */
-{
-  register char *cp;
-  bool named;
-
-  named = TRUE;
-  if (!CTAGS)
-    {
-      for (cp = name; !notinname (*cp); cp++)
-       continue;
-      if (*cp == '\0')                         /* rule #1 */
-       {
-         cp = linestart + linelen - namelen;
-         if (notinname (linestart[linelen-1]))
-           cp -= 1;                            /* rule #4 */
-         if (cp >= linestart                   /* rule #2 */
-             && (cp == linestart
-                 || notinname (cp[-1]))        /* rule #3 */
-             && strneq (name, cp, namelen))    /* rule #2 */
-           named = FALSE;      /* use unnamed tag */
-       }
-    }
-
-  if (named)
-    name = savenstr (name, namelen);
-  else
-    name = NULL;
-  pfnote (name, is_func, linestart, linelen, lno, cno);
+  add_node (np, &nodehead);
 }
 
 /*
@@ -1752,12 +2085,28 @@ free_tree (np)
       free_tree (np->left);
       if (np->name != NULL)
        free (np->name);
-      free (np->pat);
+      free (np->regex);
       free (np);
       np = node_right;
     }
 }
 
+/*
+ * free_fdesc ()
+ *     delete a file description
+ */
+static void
+free_fdesc (fdp)
+     register fdesc *fdp;
+{
+  if (fdp->infname != NULL) free (fdp->infname);
+  if (fdp->infabsname != NULL) free (fdp->infabsname);
+  if (fdp->infabsdir != NULL) free (fdp->infabsdir);
+  if (fdp->taggedfname != NULL) free (fdp->taggedfname);
+  if (fdp->prop != NULL) free (fdp->prop);
+  free (fdp);
+}
+
 /*
  * add_node ()
  *     Adds a node to the tree of nodes.  In etags mode, sort by file
@@ -1782,20 +2131,20 @@ add_node (np, cur_node_p)
     }
 
   if (!CTAGS)
+    /* Etags Mode */
     {
-      /* Etags Mode */
-      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)
+      if (last_node != NULL && last_node->fdp == np->fdp)
        {
          /* Let's use the same sublist as the last added node. */
+         assert (last_node->right == NULL);
          last_node->right = np;
          last_node = np;
        }
-      else if (streq (cur_node->file, np->file))
+      else if (cur_node->fdp == np->fdp)
        {
          /* Scanning the list we found the head of a sublist which is
             good for us.  Let's scan this sublist. */
@@ -1805,7 +2154,8 @@ add_node (np, cur_node_p)
        /* The head of this sublist is not good for us.  Let's try the
           next one. */
        add_node (np, &cur_node->left);
-    }
+    } /* if ETAGS mode */
+
   else
     {
       /* Ctags Mode */
@@ -1817,12 +2167,12 @@ add_node (np, cur_node_p)
        */
       if (!dif)
        {
-         if (streq (np->file, cur_node->file))
+         if (np->fdp == cur_node->fdp)
            {
              if (!no_warnings)
                {
                  fprintf (stderr, "Duplicate entry in file %s, line %d: %s\n",
-                          np->file, lineno, np->name);
+                          np->fdp->infname, lineno, np->name);
                  fprintf (stderr, "Second entry ignored\n");
                }
            }
@@ -1831,7 +2181,7 @@ add_node (np, cur_node_p)
              fprintf
                (stderr,
                 "Duplicate entry in files %s and %s: %s (Warning only)\n",
-                np->file, cur_node->file, np->name);
+                np->fdp->infname, cur_node->fdp->infname, np->name);
              cur_node->been_warned = TRUE;
            }
          return;
@@ -1839,6 +2189,45 @@ add_node (np, cur_node_p)
 
       /* Actually add the node */
       add_node (np, dif < 0 ? &cur_node->left : &cur_node->right);
+    } /* if CTAGS mode */
+}
+
+/*
+ * invalidate_nodes ()
+ *     Scan the node tree and invalidate all nodes pointing to the
+ *     given file description (CTAGS case) or free them (ETAGS case).
+ */
+static void
+invalidate_nodes (badfdp, npp)
+     fdesc *badfdp;
+     node **npp;
+{
+  node *np = *npp;
+
+  if (np == NULL)
+    return;
+
+  if (CTAGS)
+    {
+      if (np->left != NULL)
+       invalidate_nodes (badfdp, &np->left);
+      if (np->fdp == badfdp)
+       np->valid = FALSE;
+      if (np->right != NULL)
+       invalidate_nodes (badfdp, &np->right);
+    }
+  else
+    {
+      assert (np->fdp != NULL);
+      if (np->fdp == badfdp)
+       {
+         *npp = np->left;      /* detach the sublist from the list */
+         np->left = NULL;      /* isolate it */
+         free_tree (np);       /* free it */
+         invalidate_nodes (badfdp, npp);
+       }
+      else
+       invalidate_nodes (badfdp, &np->left);
     }
 }
 
@@ -1846,7 +2235,7 @@ add_node (np, cur_node_p)
 static int total_size_of_entries __P((node *));
 static int number_len __P((long));
 
-/* Length of a number's decimal representation. */
+/* Length of a non-negative number's decimal representation. */
 static int
 number_len (num)
      long num;
@@ -1870,15 +2259,16 @@ total_size_of_entries (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 */
-    }
+    if (np->valid)
+      {
+       total += strlen (np->regex) + 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;
 }
@@ -1888,7 +2278,7 @@ put_entries (np)
      register node *np;
 {
   register char *sp;
-  static char *file = NULL;
+  static fdesc *fdp = NULL;
 
   if (np == NULL)
     return;
@@ -1898,69 +2288,70 @@ put_entries (np)
     put_entries (np->left);
 
   /* Output this entry */
-  if (!CTAGS)
+  if (np->valid)
     {
-      /* 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)
+      if (!CTAGS)
        {
-         fputs (np->name, tagf);
-         fputc ('\001', tagf);
+         /* Etags mode */
+         if (fdp != np->fdp)
+           {
+             fdp = np->fdp;
+             fprintf (tagf, "\f\n%s,%d\n",
+                      fdp->taggedfname, total_size_of_entries (np));
+             fdp->written = TRUE;
+           }
+         fputs (np->regex, tagf);
+         fputc ('\177', tagf);
+         if (np->name != NULL)
+           {
+             fputs (np->name, tagf);
+             fputc ('\001', tagf);
+           }
+         fprintf (tagf, "%d,", np->lno);
+         if (np->cno != invalidcharno)
+           fprintf (tagf, "%ld", np->cno);
+         fputs ("\n", tagf);
        }
-      fprintf (tagf, "%d,", np->lno);
-      if (np->cno == invalidcharno)
-       fputc ('\n', tagf);
       else
-       fprintf (tagf, "%ld\n", np->cno);
-    }
-  else
-    {
-      /* Ctags mode */
-      if (np->name == NULL)
-       error ("internal error: NULL name in ctags mode.", (char *)NULL);
-
-      if (cxref_style)
        {
-         if (vgrind_style)
-           fprintf (stdout, "%s %s %d\n",
-                    np->name, np->file, (np->lno + 63) / 64);
+         /* Ctags mode */
+         if (np->name == NULL)
+           error ("internal error: NULL name in ctags mode.", (char *)NULL);
+
+         if (cxref_style)
+           {
+             if (vgrind_style)
+               fprintf (stdout, "%s %s %d\n",
+                        np->name, np->fdp->taggedfname, (np->lno + 63) / 64);
+             else
+               fprintf (stdout, "%-16s %3d %-16s %s\n",
+                        np->name, np->lno, np->fdp->taggedfname, np->regex);
+           }
          else
-           fprintf (stdout, "%-16s %3d %-16s %s\n",
-                    np->name, np->lno, np->file, np->pat);
-       }
-      else
-       {
-         fprintf (tagf, "%s\t%s\t", np->name, np->file);
+           {
+             fprintf (tagf, "%s\t%s\t", np->name, np->fdp->taggedfname);
 
-         if (np->is_func)
-           {                   /* a function */
-             putc (searchar, tagf);
-             putc ('^', tagf);
+             if (np->is_func)
+               {               /* function or #define macro with args */
+                 putc (searchar, tagf);
+                 putc ('^', tagf);
 
-             for (sp = np->pat; *sp; sp++)
-               {
-                 if (*sp == '\\' || *sp == searchar)
-                   putc ('\\', tagf);
-                 putc (*sp, tagf);
+                 for (sp = np->regex; *sp; sp++)
+                   {
+                     if (*sp == '\\' || *sp == searchar)
+                       putc ('\\', tagf);
+                     putc (*sp, tagf);
+                   }
+                 putc (searchar, tagf);
                }
-             putc (searchar, tagf);
-           }
-         else
-           {                   /* a typedef; text pattern inadequate */
-             fprintf (tagf, "%d", np->lno);
+             else
+               {               /* anything else; text pattern inadequate */
+                 fprintf (tagf, "%d", np->lno);
+               }
+             putc ('\n', tagf);
            }
-         putc ('\n', tagf);
        }
-    }
-
+    } /* if this node contains a valid tag */
 
   /* Output subentries that follow this one */
   put_entries (np->right);
@@ -2329,15 +2720,22 @@ static enum
  */
 static struct tok
 {
-  bool valid;
-  bool named;
-  int offset;
-  int length;
-  int lineno;
-  long linepos;
-  char *line;
+  char *line;                  /* string containing the token */
+  int offset;                  /* where the token starts in LINE */
+  int length;                  /* token length */
+  /*
+    The previous members can be used to pass strings around for generic
+    purposes.  The following ones specifically refer to creating tags.  In this
+    case the token contained here is the pattern that will be used to create a
+    tag.
+  */
+  bool valid;                  /* do not create a tag; the token should be
+                                  invalidated whenever a state machine is
+                                  reset prematurely */
+  bool named;                  /* create a named tag */
+  int lineno;                  /* source line number of tag */
+  long linepos;                        /* source char number of tag */
 } token;                       /* latest token read */
-static linebuffer token_name;  /* its name */
 
 /*
  * Variables and functions for dealing with nested structures.
@@ -2355,7 +2753,7 @@ static struct {
 } cstack;                      /* stack for nested declaration tags */
 /* Current struct nesting depth (namespace, class, struct, union, enum). */
 #define nestlev                (cstack.nl)
-/* After struct keyword or in struct body, not inside an nested function. */
+/* After struct keyword or in struct body, not inside a nested function. */
 #define instruct       (structdef == snone && nestlev > 0                      \
                         && cblev == cstack.cblev[nestlev-1] + 1)
 
@@ -2573,8 +2971,8 @@ consider_token (str, len, c, c_extp, cblev, parlev, is_func_or_var)
       return FALSE;
     case st_C_template:
     case st_C_class:
-      if (cblev == 0
-         && (*c_extp & C_AUTO) /* automatic detection of C++ language */
+      if ((*c_extp & C_AUTO)   /* automatic detection of C++ language */
+         && cblev == 0
          && definedef == dnone && structdef == snone
          && typdef == tnone && fvdef == fvnone)
        *c_extp = (*c_extp | C_PLPL) & ~C_AUTO;
@@ -2679,8 +3077,16 @@ consider_token (str, len, c, c_extp, cblev, parlev, is_func_or_var)
       fvextern = TRUE;
       /* FALLTHRU */
     case st_C_typespec:
-      if (fvdef != finlist && fvdef != fignore && fvdef != vignore)
-       fvdef = fvnone;         /* should be useless */
+      switch  (fvdef)
+       {
+       case finlist:
+       case flistseen:
+       case fignore:
+       case vignore:
+         break;
+       default:
+         fvdef = fvnone;
+       }
       return FALSE;
     case st_C_ignore:
       fvextern = FALSE;
@@ -2710,8 +3116,10 @@ consider_token (str, len, c, c_extp, cblev, parlev, is_func_or_var)
              fvdef = vignore;
              return FALSE;
            }
-         if ((*c_extp & C_PLPL) && strneq (str+len-10, "::operator", 10))
+         if (len >= 10 && strneq (str+len-10, "::operator", 10))
            {
+             if (*c_extp & C_AUTO) /* automatic detection of C++ */
+               *c_extp = (*c_extp | C_PLPL) & ~C_AUTO;
              fvdef = foperator;
              *is_func_or_var = TRUE;
              return TRUE;
@@ -2748,12 +3156,14 @@ static struct
 #define curlinepos (lbs[curndx].linepos)
 #define newlinepos (lbs[newndx].linepos)
 
+#define plainc ((c_ext & C_EXT) == C_PLAIN)
+#define cplpl (c_ext & C_PLPL)
+#define cjava ((c_ext & C_JAVA) == C_JAVA)
+
 #define CNL_SAVE_DEFINEDEF()                                           \
 do {                                                                   \
   curlinepos = charno;                                                 \
-  lineno++;                                                            \
-  linecharno = charno;                                                 \
-  charno += readline (&curlb, inf);                                    \
+  readline (&curlb, inf);                                              \
   lp = curlb.buffer;                                                   \
   quotednl = FALSE;                                                    \
   newndx = curndx;                                                     \
@@ -2777,31 +3187,18 @@ make_C_tag (isfun)
 {
   /* This function should never be called when token.valid is FALSE, but
      we must protect against invalid input or internal errors. */
-  if (DEBUG || token.valid)
-    {
-      if (traditional_tag_style)
-       {
-         /* This was the original code.  Now we call new_pfnote instead,
-            which uses the new method for naming tags (see new_pfnote). */
-         char *name = NULL;
+  if (!DEBUG && !token.valid)
+    return;
 
-         if (CTAGS || token.named)
-           name = savestr (token_name.buffer);
-         if (DEBUG && !token.valid)
-           {
-             if (token.named)
-               name = concat (name, "##invalid##", "");
-             else
-               name = savestr ("##invalid##");
-           }
-         pfnote (name, isfun, token.line,
-                 token.offset+token.length+1, token.lineno, token.linepos);
-       }
-      else
-       new_pfnote (token_name.buffer, token_name.len, isfun, token.line,
-                   token.offset+token.length+1, token.lineno, token.linepos);
-      token.valid = FALSE;
-    }
+  if (token.valid)
+    make_tag (token_name.buffer, token_name.len, isfun, token.line,
+             token.offset+token.length+1, token.lineno, token.linepos);
+  else                         /* this case is optimised away if !DEBUG */
+    make_tag (concat ("INVALID TOKEN:-->", token_name.buffer, ""),
+             token_name.len + 17, isfun, token.line,
+             token.offset+token.length+1, token.lineno, token.linepos);
+
+  token.valid = FALSE;
 }
 
 
@@ -2827,14 +3224,12 @@ C_entries (c_ext, inf)
   int parlev;                  /* current parenthesis level */
   int typdefcblev;             /* cblev where a typedef struct body begun */
   bool incomm, inquote, inchar, quotednl, midtoken;
-  bool cplpl, cjava;
   bool yacc_rules;             /* in the rules part of a yacc file */
   struct tok savetoken;                /* token saved during preprocessor handling */
 
 
-  initbuffer (&token_name);
-  initbuffer (&lbs[0].lb);
-  initbuffer (&lbs[1].lb);
+  linebuffer_init (&lbs[0].lb);
+  linebuffer_init (&lbs[1].lb);
   if (cstack.size == 0)
     {
       cstack.size = (DEBUG) ? 1 : 4;
@@ -2845,8 +3240,6 @@ C_entries (c_ext, inf)
 
   tokoff = toklen = typdefcblev = 0; /* keep compiler quiet */
   curndx = newndx = 0;
-  lineno = 0;
-  charno = 0;
   lp = curlb.buffer;
   *lp = 0;
 
@@ -2857,8 +3250,6 @@ C_entries (c_ext, inf)
   token.valid = savetoken.valid = FALSE;
   cblev = 0;
   parlev = 0;
-  cplpl = (c_ext & C_PLPL) == C_PLPL;
-  cjava = (c_ext & C_JAVA) == C_JAVA;
   if (cjava)
     { qualifier = "."; qlen = 1; }
   else
@@ -3024,13 +3415,13 @@ C_entries (c_ext, inf)
            {
              if (endtoken (c))
                {
-                 if (c == ':' && cplpl && *lp == ':' && begtoken (lp[1]))
+                 if (c == ':' && *lp == ':' && begtoken (lp[1]))
+                   /* This handles :: in the middle,
+                      but not at the beginning of an identifier.
+                      Also, space-separated :: is not recognised. */
                    {
-                     /*
-                      * This handles :: in the middle, but not at the
-                      * beginning of an identifier.  Also, space-separated
-                      * :: is not recognised.
-                      */
+                     if (c_ext & C_AUTO) /* automatic detection of C++ */
+                       c_ext = (c_ext | C_PLPL) & ~C_AUTO;
                      lp += 2;
                      toklen += 2;
                      c = lp[-1];
@@ -3057,7 +3448,7 @@ C_entries (c_ext, inf)
                              toklen += lp - oldlp;
                            }
                          token.named = FALSE;
-                         if ((c_ext & C_EXT)   /* not pure C */
+                         if (!plainc
                              && nestlev > 0 && definedef == dnone)
                            /* in struct body */
                            {
@@ -3173,8 +3564,11 @@ C_entries (c_ext, inf)
                      fvdef = finlist;
                      continue;
                    case flistseen:
-                     make_C_tag (TRUE); /* a function */
-                     fvdef = fignore;
+                     if (plainc || declarations)
+                       {
+                         make_C_tag (TRUE); /* a function */
+                         fvdef = fignore;
+                       }
                      break;
                    case fvnameseen:
                      fvdef = fvnone;
@@ -3188,6 +3582,7 @@ C_entries (c_ext, inf)
                  break;
                case dsharpseen:
                  savetoken = token;
+                 break;
                }
              if (!yacc_rules || lp == newlb.buffer + 1)
                {
@@ -3226,7 +3621,17 @@ C_entries (c_ext, inf)
              break;
            }
          if (structdef == stagseen)
-           structdef = scolonseen;
+           {
+             structdef = scolonseen;
+             break;
+           }
+         /* Should be useless, but may be work as a safety net. */
+         if (cplpl && fvdef == flistseen)
+           {
+             make_C_tag (TRUE); /* a function */
+             fvdef = fignore;
+             break;
+           }
          break;
        case ';':
          if (definedef != dnone)
@@ -3245,7 +3650,7 @@ C_entries (c_ext, inf)
              switch (fvdef)
                {
                case fignore:
-                 if (typdef == tignore)
+                 if (typdef == tignore || cplpl)
                    fvdef = fvnone;
                  break;
                case fvnameseen:
@@ -3257,20 +3662,17 @@ C_entries (c_ext, inf)
                  token.valid = FALSE;
                  break;
                case flistseen:
-                 if ((declarations && typdef == tnone && !instruct)
-                     || (members && typdef != tignore && instruct))
+                 if (declarations
+                     && (typdef == tnone || (typdef != tignore && instruct)))
                    make_C_tag (TRUE);  /* a function declaration */
                  /* FALLTHRU */
                default:
                  fvextern = FALSE;
                  fvdef = fvnone;
                  if (declarations
-                     && structdef == stagseen && (c_ext & C_PLPL))
+                      && cplpl && structdef == stagseen)
                    make_C_tag (FALSE); /* forward declaration */
                  else
-                   /* The following instruction invalidates the token.
-                      Probably the token should be invalidated in all other
-                      cases where some state machine is reset prematurely. */
                    token.valid = FALSE;
                } /* switch (fvdef) */
              /* FALLTHRU */
@@ -3452,6 +3854,7 @@ C_entries (c_ext, inf)
                      && typdef == tnone)
                    cblev = -1;
                }
+             break;
            }
          switch (structdef)
            {
@@ -3472,18 +3875,25 @@ C_entries (c_ext, inf)
          if (definedef != dnone)
            break;
          if (fvdef == fstartlist)
-           fvdef = fvnone;     /* avoid tagging `foo' in `foo (*bar()) ()' */
+           {
+             fvdef = fvnone;   /* avoid tagging `foo' in `foo (*bar()) ()' */
+             token.valid = FALSE;
+           }
          break;
        case '}':
          if (definedef != dnone)
            break;
-         if (!noindentypedefs && lp == newlb.buffer + 1)
+         if (!ignoreindent && lp == newlb.buffer + 1)
            {
+             if (cblev != 0)
+               token.valid = FALSE;
              cblev = 0;        /* reset curly brace level if first column */
              parlev = 0;       /* also reset paren level, just in case... */
            }
          else if (cblev > 0)
            cblev--;
+         else
+           token.valid = FALSE; /* something gone amiss, token unreliable */
          popclass_above (cblev);
          structdef = snone;
          /* Only if typdef == tinbody is typdefcblev significant. */
@@ -3567,7 +3977,6 @@ C_entries (c_ext, inf)
 
     } /* while not eof */
 
-  free (token_name.buffer);
   free (lbs[0].lb.buffer);
   free (lbs[1].lb.buffer);
 }
@@ -3626,12 +4035,11 @@ Yacc_entries (inf)
 \f
 /* Useful macros. */
 #define LOOP_ON_INPUT_LINES(file_pointer, line_buffer, char_pointer)   \
-  for (lineno = charno = 0;    /* loop initialization */               \
+  for (;                       /* loop initialization */               \
        !feof (file_pointer)    /* loop test */                         \
-       && (lineno++,           /* instructions at start of loop */     \
-          linecharno = charno,                                         \
-          charno += readline (&line_buffer, file_pointer),             \
-          char_pointer = lb.buffer,                                    \
+       &&                      /* instructions at start of loop */     \
+         (readline (&line_buffer, file_pointer),                       \
+           char_pointer = line_buffer.buffer,                          \
           TRUE);                                                       \
       )
 #define LOOKING_AT(cp, keyword)        /* keyword is a constant string */      \
@@ -3691,9 +4099,7 @@ F_getit (inf)
   dbp = skip_spaces (dbp);
   if (*dbp == '\0')
     {
-      lineno++;
-      linecharno = charno;
-      charno += readline (&lb, inf);
+      readline (&lb, inf);
       dbp = lb.buffer;
       if (dbp[5] != '&')
        return;
@@ -3704,8 +4110,8 @@ F_getit (inf)
     return;
   for (cp = dbp + 1; *cp != '\0' && intoken (*cp); cp++)
     continue;
-  pfnote (savenstr (dbp, cp-dbp), TRUE,
-         lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+  make_tag (dbp, cp-dbp, TRUE,
+           lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
 }
 
 
@@ -3772,8 +4178,8 @@ Fortran_functions (inf)
            {
              dbp = skip_spaces (dbp);
              if (*dbp == '\0') /* assume un-named */
-               pfnote (savestr ("blockdata"), TRUE,
-                       lb.buffer, dbp - lb.buffer, lineno, linecharno);
+               make_tag ("blockdata", 9, TRUE,
+                         lb.buffer, dbp - lb.buffer, lineno, linecharno);
              else
                F_getit (inf);  /* look for name */
            }
@@ -3786,7 +4192,7 @@ Fortran_functions (inf)
 /*
  * Ada parsing
  * Original code by
- * Philippe Waroquiers <philippe.waroquiers@eurocontrol.be> (1998)
+ * Philippe Waroquiers (1998)
  */
 
 static void Ada_getit __P((FILE *, char *));
@@ -3808,9 +4214,7 @@ Ada_getit (inf, name_qualifier)
       if (*dbp == '\0'
          || (dbp[0] == '-' && dbp[1] == '-'))
        {
-         lineno++;
-         linecharno = charno;
-         charno += readline (&lb, inf);
+         readline (&lb, inf);
          dbp = lb.buffer;
        }
       switch (lowcase(*dbp))
@@ -3851,7 +4255,9 @@ Ada_getit (inf, name_qualifier)
       *cp = '\0';
       name = concat (dbp, name_qualifier, "");
       *cp = c;
-      pfnote (name, TRUE, lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+      make_tag (name, strlen (name), TRUE,
+               lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+      free (name);
       if (c == '"')
        dbp = cp + 1;
       return;
@@ -3863,6 +4269,7 @@ Ada_funcs (inf)
      FILE *inf;
 {
   bool inquote = FALSE;
+  bool skip_till_semicolumn = FALSE;
 
   LOOP_ON_INPUT_LINES (inf, lb, dbp)
     {
@@ -3899,6 +4306,14 @@ Ada_funcs (inf)
              continue;
            }
 
+         if (skip_till_semicolumn)
+           {
+             if (*dbp == ';')
+               skip_till_semicolumn = FALSE;
+             dbp++;
+             continue;         /* advance char */
+           }
+
          /* Search for beginning of a token.  */
          if (!begtoken (*dbp))
            {
@@ -3925,6 +4340,16 @@ Ada_funcs (inf)
              else
                break;          /* from switch */
              continue;         /* advance char */
+
+           case 'u':
+             if (typedefs && !packages_only && nocase_tail ("use"))
+               {
+                 /* when tagging types, avoid tagging  use type Pack.Typename;
+                    for this, we will skip everything till a ; */
+                 skip_till_semicolumn = TRUE;
+                 continue;     /* advance char */
+               }
+
            case 't':
              if (!packages_only && nocase_tail ("task"))
                Ada_getit (inf, "/k");
@@ -3970,11 +4395,9 @@ Asm_labels (inf)
          while (ISALNUM (*cp) || *cp == '_' || *cp == '.' || *cp == '$')
            cp++;
          if (*cp == ':' || iswhite (*cp))
-           {
-             /* Found end of label, so copy it and add it to the table. */
-             pfnote (savenstr(lb.buffer, cp-lb.buffer), TRUE,
+           /* Found end of label, so copy it and add it to the table. */
+           make_tag (lb.buffer, cp - lb.buffer, TRUE,
                      lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
-           }
        }
     }
 }
@@ -4002,56 +4425,63 @@ Perl_functions (inf)
       if (LOOKING_AT (cp, "package"))
        {
          free (package);
-         package = get_tag (cp);
-         if (package == NULL)  /* can't parse package name */
-           package = savestr ("");
-         else
-           package = savestr(package); /* make a copy */
+         get_tag (cp, &package);
        }
       else if (LOOKING_AT (cp, "sub"))
        {
-         char *name, *fullname, *pos;
+         char *pos;
          char *sp = cp;
 
          while (!notinname (*cp))
            cp++;
          if (cp == sp)
-           continue;
-         name = savenstr (sp, cp-sp);
-         if ((pos = etags_strchr (name, ':')) != NULL && pos[1] == ':')
-           fullname = name;
+           continue;           /* nothing found */
+         if ((pos = etags_strchr (sp, ':')) != NULL
+             && pos < cp && pos[1] == ':')
+           /* The name is already qualified. */
+           make_tag (sp, cp - sp, TRUE,
+                     lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
          else
-           fullname = concat (package, "::", name);
-         pfnote (fullname, TRUE,
-                 lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
-         if (name != fullname)
-           free (name);
+           /* Qualify it. */
+           {
+             char savechar, *name;
+
+             savechar = *cp;
+             *cp = '\0';
+             name = concat (package, "::", sp);
+             *cp = savechar;
+             make_tag (name, strlen(name), TRUE,
+                       lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+             free (name);
+           }
        }
-       else if (globals                /* only if tagging global vars is enabled */
-               && (LOOKING_AT (cp, "my") || LOOKING_AT (cp, "local")))
+       else if (globals)       /* only if we are tagging global vars */
        {
+         /* Skip a qualifier, if any. */
+         bool qual = LOOKING_AT (cp, "my") || LOOKING_AT (cp, "local");
          /* After "my" or "local", but before any following paren or space. */
-         char *varname = NULL;
+         char *varstart = cp;
 
-         if (*cp == '$' || *cp == '@' || *cp == '%')
+         if (qual              /* should this be removed?  If yes, how? */
+             && (*cp == '$' || *cp == '@' || *cp == '%'))
            {
-             char* varstart = ++cp;
-             while (ISALNUM (*cp) || *cp == '_')
+             varstart += 1;
+             do
                cp++;
-             varname = savenstr (varstart, cp-varstart);
+             while (ISALNUM (*cp) || *cp == '_');
            }
-         else
+         else if (qual)
            {
              /* Should be examining a variable list at this point;
                 could insist on seeing an open parenthesis. */
              while (*cp != '\0' && *cp != ';' && *cp != '=' &&  *cp != ')')
                cp++;
            }
+         else
+           continue;
 
-         /* Perhaps I should back cp up one character, so the TAGS table
-            doesn't mention (and so depend upon) the following char. */
-         pfnote (varname, FALSE,
-                 lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+         make_tag (varstart, cp - varstart, FALSE,
+                   lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
        }
     }
 }
@@ -4077,8 +4507,8 @@ Python_functions (inf)
          char *name = cp;
          while (!notinname (*cp) && *cp != ':')
            cp++;
-         pfnote (savenstr (name, cp-name), TRUE,
-                 lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+         make_tag (name, cp - name, TRUE,
+                   lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
        }
     }
 }
@@ -4110,8 +4540,8 @@ PHP_functions (inf)
        {
          while (!notinname (*cp))
            cp++;
-         pfnote (savenstr (name, cp-name), TRUE,
-                 lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+         make_tag (name, cp - name, TRUE,
+                   lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
          search_identifier = FALSE;
        }
       else if (LOOKING_AT (cp, "function"))
@@ -4123,8 +4553,8 @@ PHP_functions (inf)
              name = cp;
              while (!notinname (*cp))
                cp++;
-             pfnote (savenstr (name, cp-name), TRUE,
-                     lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+             make_tag (name, cp - name, TRUE,
+                       lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
            }
          else
            search_identifier = TRUE;
@@ -4136,8 +4566,8 @@ PHP_functions (inf)
              name = cp;
              while (*cp != '\0' && !iswhite (*cp))
                cp++;
-             pfnote (savenstr (name, cp-name), FALSE,
-                     lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+             make_tag (name, cp - name, FALSE,
+                       lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
            }
          else
            search_identifier = TRUE;
@@ -4151,8 +4581,8 @@ PHP_functions (inf)
          name = cp;
          while (*cp != quote && *cp != '\0')
            cp++;
-         pfnote (savenstr (name, cp-name), FALSE,
-                 lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+         make_tag (name, cp - name, FALSE,
+                   lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
        }
       else if (members
               && LOOKING_AT (cp, "var")
@@ -4161,8 +4591,8 @@ PHP_functions (inf)
          name = cp;
          while (!notinname(*cp))
            cp++;
-         pfnote (savenstr (name, cp-name), FALSE,
-                 lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+         make_tag (name, cp - name, FALSE,
+                   lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
        }
     }
 }
@@ -4193,15 +4623,15 @@ Cobol_paragraphs (inf)
       for (ep = bp; ISALNUM (*ep) || *ep == '-'; ep++)
        continue;
       if (*ep++ == '.')
-       pfnote (savenstr (bp, ep-bp), TRUE,
-               lb.buffer, ep - lb.buffer + 1, lineno, linecharno);
+       make_tag (bp, ep - bp, TRUE,
+                 lb.buffer, ep - lb.buffer + 1, lineno, linecharno);
     }
 }
 
 \f
 /*
  * Makefile support
- * Idea by Assar Westerlund <assar@sics.se> (2001)
+ * Ideas by Assar Westerlund <assar@sics.se> (2001)
  */
 static void
 Makefile_targets (inf)
@@ -4215,9 +4645,9 @@ Makefile_targets (inf)
        continue;
       while (*bp != '\0' && *bp != '=' && *bp != ':')
        bp++;
-      if (*bp == ':')
-       pfnote (savenstr (lb.buffer, bp - lb.buffer), TRUE,
-               lb.buffer, bp - lb.buffer + 1, lineno, linecharno);
+      if (*bp == ':' || (globals && *bp == '='))
+       make_tag (lb.buffer, bp - lb.buffer, TRUE,
+                 lb.buffer, bp - lb.buffer + 1, lineno, linecharno);
     }
 }
 
@@ -4237,8 +4667,8 @@ Pascal_functions (inf)
 {
   linebuffer tline;            /* mostly copied from C_entries */
   long save_lcno;
-  int save_lineno, save_len;
-  char c, *cp, *namebuf;
+  int save_lineno, namelen, taglen;
+  char c, *name;
 
   bool                         /* each of these flags is TRUE iff: */
     incomment,                 /* point is inside a comment */
@@ -4252,17 +4682,15 @@ Pascal_functions (inf)
                                   is a FORWARD/EXTERN to be ignored, or
                                   whether it is a real tag */
 
-  save_lcno = save_lineno = save_len = 0; /* keep compiler quiet */
-  namebuf = NULL;              /* keep compiler quiet */
-  lineno = 0;
-  charno = 0;
+  save_lcno = save_lineno = namelen = taglen = 0; /* keep compiler quiet */
+  name = NULL;                 /* keep compiler quiet */
   dbp = lb.buffer;
   *dbp = '\0';
-  initbuffer (&tline);
+  linebuffer_init (&tline);
 
   incomment = inquote = FALSE;
   found_tag = FALSE;           /* have a proc name; check if extern */
-  get_tagname = FALSE;         /* have found "procedure" keyword    */
+  get_tagname = FALSE;         /* found "procedure" keyword         */
   inparms = FALSE;             /* found '(' after "proc"            */
   verify_tag = FALSE;          /* check if "extern" is ahead        */
 
@@ -4272,9 +4700,7 @@ Pascal_functions (inf)
       c = *dbp++;
       if (c == '\0')           /* if end of line */
        {
-         lineno++;
-         linecharno = charno;
-         charno += readline (&lb, inf);
+         readline (&lb, inf);
          dbp = lb.buffer;
          if (*dbp == '\0')
            continue;
@@ -4333,7 +4759,7 @@ Pascal_functions (inf)
          }
       if (found_tag && verify_tag && (*dbp != ' '))
        {
-         /* check if this is an "extern" declaration */
+         /* Check if this is an "extern" declaration. */
          if (*dbp == '\0')
            continue;
          if (lowcase (*dbp == 'e'))
@@ -4346,7 +4772,7 @@ Pascal_functions (inf)
            }
          else if (lowcase (*dbp) == 'f')
            {
-             if (nocase_tail ("forward")) /*  check for forward reference */
+             if (nocase_tail ("forward")) /* check for forward reference */
                {
                  found_tag = FALSE;
                  verify_tag = FALSE;
@@ -4356,37 +4782,41 @@ Pascal_functions (inf)
            {
              found_tag = FALSE;
              verify_tag = FALSE;
-             pfnote (namebuf, TRUE,
-                     tline.buffer, save_len, save_lineno, save_lcno);
+             make_tag (name, namelen, TRUE,
+                       tline.buffer, taglen, save_lineno, save_lcno);
              continue;
            }
        }
       if (get_tagname)         /* grab name of proc or fn */
        {
+         char *cp;
+
          if (*dbp == '\0')
            continue;
 
-         /* save all values for later tagging */
+         /* Find block name. */
+         for (cp = dbp + 1; *cp != '\0' && !endtoken (*cp); cp++)
+           continue;
+
+         /* Save all values for later tagging. */
          linebuffer_setlen (&tline, lb.len);
          strcpy (tline.buffer, lb.buffer);
          save_lineno = lineno;
          save_lcno = linecharno;
+         name = tline.buffer + (dbp - lb.buffer);
+         namelen = cp - dbp;
+         taglen = cp - lb.buffer + 1;
 
-         /* grab block name */
-         for (cp = dbp + 1; *cp != '\0' && !endtoken (*cp); cp++)
-           continue;
-         namebuf = savenstr (dbp, cp-dbp);
          dbp = cp;             /* set dbp to e-o-token */
-         save_len = dbp - lb.buffer + 1;
          get_tagname = FALSE;
          found_tag = TRUE;
          continue;
 
-         /* and proceed to check for "extern" */
+         /* And proceed to check for "extern". */
        }
       else if (!incomment && !inquote && !found_tag)
        {
-         /* check for proc/fn keywords */
+         /* Check for proc/fn keywords. */
          switch (lowcase (c))
            {
            case 'p':
@@ -4399,7 +4829,7 @@ Pascal_functions (inf)
              continue;
            }
        }
-    }                          /* while not eof */
+    } /* while not eof */
 
   free (tline.buffer);
 }
@@ -4425,7 +4855,7 @@ L_getit ()
       /* Ok, then skip "(" before name in (defstruct (foo)) */
       dbp = skip_spaces (dbp);
   }
-  get_tag (dbp);
+  get_tag (dbp, NULL);
 }
 
 static void
@@ -4476,7 +4906,7 @@ Lisp_functions (inf)
  *   Masatake Yamato <masata-y@is.aist-nara.ac.jp> (1999)
  */
 static void
-Postscript_functions (inf)
+PS_functions (inf)
      FILE *inf;
 {
   register char *bp, *ep;
@@ -4489,11 +4919,11 @@ Postscript_functions (inf)
               *ep != '\0' && *ep != ' ' && *ep != '{';
               ep++)
            continue;
-         pfnote (savenstr (bp, ep-bp), TRUE,
-                 lb.buffer, ep - lb.buffer + 1, lineno, linecharno);
+         make_tag (bp, ep - bp, TRUE,
+                   lb.buffer, ep - lb.buffer + 1, lineno, linecharno);
        }
       else if (LOOKING_AT (bp, "defineps"))
-       get_tag (bp);
+       get_tag (bp, NULL);
     }
 }
 
@@ -4521,10 +4951,10 @@ Scheme_functions (inf)
          /* Skip over open parens and white space */
          while (notinname (*bp))
            bp++;
-         get_tag (bp);
+         get_tag (bp, NULL);
        }
       if (LOOKING_AT (bp, "(SET!") || LOOKING_AT (bp, "(set!"))
-       get_tag (bp);
+       get_tag (bp, NULL);
     }
 }
 
@@ -4532,26 +4962,24 @@ Scheme_functions (inf)
 /* Find tags in TeX and LaTeX input files.  */
 
 /* TEX_toktab is a table of TeX control sequences that define tags.
-   Each TEX_tabent records one such control sequence.
-   CONVERT THIS TO USE THE Stab TYPE!! */
-struct TEX_tabent
-{
-  char *name;
-  int len;
-};
+ * Each entry records one such control sequence.
+ *
+ * Original code from who knows whom.
+ * Ideas by:
+ *   Stefan Monnier (2002)
+ */
 
-static struct TEX_tabent *TEX_toktab = NULL; /* Table with tag tokens */
+static linebuffer *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.  */
-
 static char *TEX_defenv = "\
 :chapter:section:subsection:subsubsection:eqno:label:ref:cite:bibitem\
-:part:appendix:entry:index";
+:part:appendix:entry:index:def\
+:newcommand:renewcommand:newenvironment:renewenvironment";
 
 static void TEX_mode __P((FILE *));
-static struct TEX_tabent *TEX_decode_env __P((char *, char *));
-static int TEX_Token __P((char *));
+static void TEX_decode_env __P((char *, char *));
 
 static char TEX_esc = '\\';
 static char TEX_opgrp = '{';
@@ -4564,49 +4992,64 @@ static void
 TeX_commands (inf)
      FILE *inf;
 {
-  char *cp, *lasthit;
-  register int i;
+  char *cp;
+  linebuffer *key;
 
   /* Select either \ or ! as escape character.  */
   TEX_mode (inf);
 
   /* Initialize token table once from environment. */
-  if (!TEX_toktab)
-    TEX_toktab = TEX_decode_env ("TEXTAGS", TEX_defenv);
+  if (TEX_toktab == NULL)
+    TEX_decode_env ("TEXTAGS", TEX_defenv);
 
   LOOP_ON_INPUT_LINES (inf, lb, cp)
     {
-      lasthit = cp;
-      /* Look at each esc in line. */
-      while ((cp = etags_strchr (cp, TEX_esc)) != NULL)
+      /* Look at each TEX keyword in line. */
+      for (;;)
        {
-         if (*++cp == '\0')
-           break;
-         linecharno += cp - lasthit;
-         lasthit = cp;
-         i = TEX_Token (lasthit);
-         if (i >= 0)
-           {
-             register char *p;
-             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), TRUE,
-                     lb.buffer, lb.len, lineno, linecharno);
-             break;            /* We only tag a line once */
-           }
+         /* Look for a TEX escape. */
+         while (*cp++ != TEX_esc)
+           if (cp[-1] == '\0' || cp[-1] == '%')
+             goto tex_next_line;
+
+         for (key = TEX_toktab; key->buffer != NULL; key++)
+           if (strneq (cp, key->buffer, key->len))
+             {
+               register char *p;
+               int namelen, linelen;
+               bool opgrp = FALSE;
+
+               cp = skip_spaces (cp + key->len);
+               if (*cp == TEX_opgrp)
+                 {
+                   opgrp = TRUE;
+                   cp++;
+                 }
+               for (p = cp;
+                    (!iswhite (*p) && *p != '#' &&
+                     *p != TEX_opgrp && *p != TEX_clgrp);
+                    p++)
+                 continue;
+               namelen = p - cp;
+               linelen = lb.len;
+               if (!opgrp || *p == TEX_clgrp)
+                 {
+                   while (*p != '\0' && *p != TEX_opgrp && *p != TEX_clgrp)
+                     *p++;
+                   linelen = p - lb.buffer + 1;
+                 }
+               make_tag (cp, namelen, TRUE,
+                         lb.buffer, linelen, lineno, linecharno);
+               goto tex_next_line; /* We only tag a line once */
+             }
        }
+    tex_next_line:
+      ;
     }
 }
 
 #define TEX_LESC '\\'
 #define TEX_SESC '!'
-#define TEX_cmt  '%'
 
 /* Figure out whether TeX's escapechar is '\\' or '!' and set grouping
    chars accordingly. */
@@ -4619,7 +5062,7 @@ TEX_mode (inf)
   while ((c = getc (inf)) != EOF)
     {
       /* Skip to next line if we hit the TeX comment char. */
-      if (c == TEX_cmt)
+      if (c == '%')
        while (c != '\n')
          c = getc (inf);
       else if (c == TEX_LESC || c == TEX_SESC )
@@ -4645,15 +5088,13 @@ TEX_mode (inf)
 
 /* Read environment and prepend it to the default string.
    Build token table. */
-static struct TEX_tabent *
+static void
 TEX_decode_env (evarname, defenv)
      char *evarname;
      char *defenv;
 {
   register char *env, *p;
-
-  struct TEX_tabent *tab;
-  int size, i;
+  int i, len;
 
   /* Append default string to environment. */
   env = getenv (evarname);
@@ -4666,52 +5107,33 @@ TEX_decode_env (evarname, defenv)
     }
 
   /* Allocate a token table */
-  for (size = 1, p = env; p;)
+  for (len = 1, p = env; p;)
     if ((p = etags_strchr (p, ':')) && *++p != '\0')
-      size++;
-  /* Add 1 to leave room for null terminator.  */
-  tab = xnew (size + 1, struct TEX_tabent);
+      len++;
+  TEX_toktab = xnew (len, linebuffer);
 
   /* Unpack environment string into token table. Be careful about */
   /* zero-length strings (leading ':', "::" and trailing ':') */
-  for (i = 0; *env;)
+  for (i = 0; *env != '\0';)
     {
       p = etags_strchr (env, ':');
       if (!p)                  /* End of environment string. */
        p = env + strlen (env);
       if (p - env > 0)
        {                       /* Only non-zero strings. */
-         tab[i].name = savenstr (env, p - env);
-         tab[i].len = strlen (tab[i].name);
+         TEX_toktab[i].buffer = savenstr (env, p - env);
+         TEX_toktab[i].len = p - env;
          i++;
        }
       if (*p)
        env = p + 1;
       else
        {
-         tab[i].name = NULL;   /* Mark end of table. */
-         tab[i].len = 0;
+         TEX_toktab[i].buffer = NULL; /* Mark end of table. */
+         TEX_toktab[i].len = 0;
          break;
        }
     }
-  return tab;
-}
-
-/* If the text at CP matches one of the tag-defining TeX command names,
-   return the pointer to the first occurrence of that command in TEX_toktab.
-   Otherwise return -1.
-   Keep the capital `T' in `token' for dumb truncating compilers
-   (this distinguishes it from `TEX_toktab' */
-static int
-TEX_Token (cp)
-     char *cp;
-{
-  int i;
-
-  for (i = 0; TEX_toktab[i].len > 0; i++)
-    if (strneq (TEX_toktab[i].name, cp, TEX_toktab[i].len))
-      return i;
-  return -1;
 }
 
 \f
@@ -4727,8 +5149,134 @@ Texinfo_nodes (inf)
        start = cp;
        while (*cp != '\0' && *cp != ',')
          cp++;
-       pfnote (savenstr (start, cp - start), TRUE,
-               lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+       make_tag (start, cp - start, TRUE,
+                 lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+      }
+}
+
+\f
+/* Similar to LOOKING_AT but does not use notinname, does not skip */
+#define LOOKING_AT_NOCASE(cp, kw)      /* kw is a constant string */   \
+  (strncaseeq ((cp), kw, sizeof(kw)-1) /* cp points at kw */           \
+   && ((cp) += sizeof(kw)-1))          /* skip spaces */
+
+/*
+ * HTML support.
+ * Contents of <title>, <h1>, <h2>, <h3> are tags.
+ * Contents of <a name=xxx> are tags with name xxx.
+ *
+ * Francesco Potortì, 2002.
+ */
+static void
+HTML_labels (inf)
+     FILE * inf;
+{
+  bool getnext = FALSE;                /* next text outside of HTML tags is a tag */
+  bool skiptag = FALSE;                /* skip to the end of the current HTML tag */
+  bool intag = FALSE;          /* inside an html tag, looking for ID= */
+  bool inanchor = FALSE;       /* when INTAG, is an anchor, look for NAME= */
+  char *end;
+
+
+  linebuffer_setlen (&token_name, 0); /* no name in buffer */
+
+  LOOP_ON_INPUT_LINES (inf, lb, dbp)
+    for (;;)                   /* loop on the same line */
+      {
+       if (skiptag)            /* skip HTML tag */
+         {
+           while (*dbp != '\0' && *dbp != '>')
+             dbp++;
+           if (*dbp == '>')
+             {
+               dbp += 1;
+               skiptag = FALSE;
+               continue;       /* look on the same line */
+             }
+           break;              /* go to next line */
+         }
+
+       else if (intag) /* look for "name=" or "id=" */
+         {
+           while (*dbp != '\0' && *dbp != '>'
+                  && lowcase (*dbp) != 'n' && lowcase (*dbp) != 'i')
+             dbp++;
+           if (*dbp == '\0')
+             break;            /* go to next line */
+           if (*dbp == '>')
+             {
+               dbp += 1;
+               intag = FALSE;
+               continue;       /* look on the same line */
+             }
+           if ((inanchor && LOOKING_AT_NOCASE (dbp, "name="))
+               || LOOKING_AT_NOCASE (dbp, "id="))
+             {
+               bool quoted = (dbp[0] == '"');
+
+               if (quoted)
+                 for (end = ++dbp; *end != '\0' && *end != '"'; end++)
+                   continue;
+               else
+                 for (end = dbp; *end != '\0' && intoken (*end); end++)
+                   continue;
+               linebuffer_setlen (&token_name, end - dbp);
+               strncpy (token_name.buffer, dbp, end - dbp);
+               token_name.buffer[end - dbp] = '\0';
+
+               dbp = end;
+               intag = FALSE;  /* we found what we looked for */
+               skiptag = TRUE; /* skip to the end of the tag */
+               getnext = TRUE; /* then grab the text */
+               continue;       /* look on the same line */
+             }
+           dbp += 1;
+         }
+
+       else if (getnext)       /* grab next tokens and tag them */
+         {
+           dbp = skip_spaces (dbp);
+           if (*dbp == '\0')
+             break;            /* go to next line */
+           if (*dbp == '<')
+             {
+               intag = TRUE;
+               inanchor = (lowcase (dbp[1]) == 'a' && !intoken (dbp[2]));
+               continue;       /* look on the same line */
+             }
+
+           for (end = dbp + 1; *end != '\0' && *end != '<'; end++)
+             continue;
+           make_tag (token_name.buffer, token_name.len, TRUE,
+                     dbp, end - dbp, lineno, linecharno);
+           linebuffer_setlen (&token_name, 0); /* no name in buffer */
+           getnext = FALSE;
+           break;              /* go to next line */
+         }
+
+       else                    /* look for an interesting HTML tag */
+         {
+           while (*dbp != '\0' && *dbp != '<')
+             dbp++;
+           if (*dbp == '\0')
+             break;            /* go to next line */
+           intag = TRUE;
+           if (lowcase (dbp[1]) == 'a' && !intoken (dbp[2]))
+             {
+               inanchor = TRUE;
+               continue;       /* look on the same line */
+             }
+           else if (LOOKING_AT_NOCASE (dbp, "<title>")
+                    || LOOKING_AT_NOCASE (dbp, "<h1>")
+                    || LOOKING_AT_NOCASE (dbp, "<h2>")
+                    || LOOKING_AT_NOCASE (dbp, "<h3>"))
+             {
+               intag = FALSE;
+               getnext = TRUE;
+               continue;       /* look on the same line */
+             }
+           dbp += 1;
+         }
       }
 }
 
@@ -4793,8 +5341,7 @@ prolog_skip_comment (plb, inf)
       for (cp = plb->buffer; *cp != '\0'; cp++)
        if (cp[0] == '*' && cp[1] == '/')
          return;
-      lineno++;
-      linecharno += readline (plb, inf);
+      readline (plb, inf);
     }
   while (!feof(inf));
 }
@@ -4832,7 +5379,7 @@ prolog_pr (s, last)
          || len != strlen (last)
          || !strneq (s, last, len)))
        {
-         pfnote (savenstr (s, len), TRUE, s, pos, lineno, linecharno);
+         make_tag (s, len, TRUE, s, pos, lineno, linecharno);
          return len;
        }
   else
@@ -4871,7 +5418,7 @@ prolog_atom (s, pos)
     {
       pos++;
 
-      while (1)
+      for (;;)
        {
          if (s[pos] == '\'')
            {
@@ -4908,7 +5455,7 @@ prolog_atom (s, pos)
  */
 static int erlang_func __P((char *, char *));
 static void erlang_attribute __P((char *));
-static int erlang_atom __P((char *, int));
+static int erlang_atom __P((char *));
 
 static void
 Erlang_functions (inf)
@@ -4973,7 +5520,7 @@ erlang_func (s, last)
   int pos;
   int len;
 
-  pos = erlang_atom (s, 0);
+  pos = erlang_atom (s);
   if (pos < 1)
     return 0;
 
@@ -4986,7 +5533,7 @@ erlang_func (s, last)
          || len != (int)strlen (last)
          || !strneq (s, last, len)))
        {
-         pfnote (savenstr (s, len), TRUE, s, pos, lineno, linecharno);
+         make_tag (s, len, TRUE, s, pos, lineno, linecharno);
          return len;
        }
 
@@ -5007,19 +5554,14 @@ static void
 erlang_attribute (s)
      char *s;
 {
-  int pos;
-  int len;
+  char *cp = s;
 
-  if (LOOKING_AT (s, "-define") || LOOKING_AT (s, "-record"))
+  if ((LOOKING_AT (cp, "-define") || LOOKING_AT (cp, "-record"))
+      && *cp++ == '(')
     {
-      if (s[pos++] == '(')
-       {
-         pos = skip_spaces (s + pos) - s;
-         len = erlang_atom (s, pos);
-         if (len != 0)
-           pfnote (savenstr (& s[pos], len), TRUE,
-                   s, pos + len, lineno, linecharno);
-       }
+      int len = erlang_atom (skip_spaces (cp));
+      if (len > 0)
+       make_tag (cp, len, TRUE, s, cp + len - s, lineno, linecharno);
     }
   return;
 }
@@ -5030,65 +5572,46 @@ erlang_attribute (s)
  * Return the number of bytes consumed, or -1 if there was an error.
  */
 static int
-erlang_atom (s, pos)
+erlang_atom (s)
      char *s;
-     int pos;
 {
-  int origpos;
-
-  origpos = pos;
+  int pos = 0;
 
   if (ISALPHA (s[pos]) || s[pos] == '_')
     {
       /* The atom is unquoted. */
-      pos++;
-      while (ISALNUM (s[pos]) || s[pos] == '_')
+      do
        pos++;
-      return pos - origpos;
+      while (ISALNUM (s[pos]) || s[pos] == '_');
     }
   else if (s[pos] == '\'')
     {
+      for (pos++; s[pos] != '\''; pos++)
+       if (s[pos] == '\0'      /* multiline quoted atoms are ignored */
+           || (s[pos] == '\\' && s[++pos] == '\0'))
+         return 0;
       pos++;
-
-      while (1)
-       {
-         if (s[pos] == '\'')
-           {
-             pos++;
-             break;
-           }
-         else if (s[pos] == '\0')
-           /* Multiline quoted atoms are ignored. */
-           return -1;
-         else if (s[pos] == '\\')
-           {
-             if (s[pos+1] == '\0')
-               return -1;
-             pos += 2;
-           }
-         else
-           pos++;
-       }
-      return pos - origpos;
     }
-  else
-    return -1;
+
+  return pos;
 }
 
 \f
 #ifdef ETAGS_REGEXPS
 
 static char *scan_separators __P((char *));
-static void analyse_regex __P((char *, bool));
-static void add_regex __P((char *, bool, language *));
+static void add_regex __P((char *, language *));
 static char *substitute __P((char *, char *, struct re_registers *));
 
-/* Take a string like "/blah/" and turn it into "blah", making sure
-   that the first and last characters are the same, and handling
-   quoted separator characters.  Actually, stops on the occurrence of
-   an unquoted separator.  Also turns "\t" into a Tab character.
-   Returns pointer to terminating separator.  Works in place.  Null
-   terminates name string. */
+/*
+ * Take a string like "/blah/" and turn it into "blah", verifying
+ * that the first and last characters are the same, and handling
+ * quoted separator characters.  Actually, stops on the occurrence of
+ * an unquoted separator.  Also process \t, \n, etc. and turn into
+ * appropriate characters. Works in place.  Null terminates name string.
+ * Returns pointer to terminating separator, or NULL for
+ * unterminated regexps.
+ */
 static char *
 scan_separators (name)
      char *name;
@@ -5101,15 +5624,27 @@ scan_separators (name)
     {
       if (quoted)
        {
-         if (*name == 't')
-           *copyto++ = '\t';
-         else if (*name == sep)
-           *copyto++ = sep;
-         else
+         switch (*name)
            {
-             /* Something else is quoted, so preserve the quote. */
-             *copyto++ = '\\';
-             *copyto++ = *name;
+           case 'a': *copyto++ = '\007'; break; /* BEL (bell)           */
+           case 'b': *copyto++ = '\b'; break;   /* BS (back space)      */
+           case 'd': *copyto++ = 0177; break;   /* DEL (delete)         */
+           case 'e': *copyto++ = 033; break;    /* ESC (delete)         */
+           case 'f': *copyto++ = '\f'; break;   /* FF (form feed)       */
+           case 'n': *copyto++ = '\n'; break;   /* NL (new line)        */
+           case 'r': *copyto++ = '\r'; break;   /* CR (carriage return) */
+           case 't': *copyto++ = '\t'; break;   /* TAB (horizontal tab) */
+           case 'v': *copyto++ = '\v'; break;   /* VT (vertical tab)    */
+           default:
+             if (*name == sep)
+               *copyto++ = sep;
+             else
+               {
+                 /* Something else is quoted, so preserve the quote. */
+                 *copyto++ = '\\';
+                 *copyto++ = *name;
+               }
+             break;
            }
          quoted = FALSE;
        }
@@ -5120,6 +5655,8 @@ scan_separators (name)
       else
        *copyto++ = *name;
     }
+  if (*name != sep)
+    name = NULL;               /* signal unterminated regexp */
 
   /* Terminate copied string. */
   *copyto = '\0';
@@ -5129,13 +5666,12 @@ scan_separators (name)
 /* Look at the argument of --regex or --no-regex and do the right
    thing.  Same for each line of a regexp file. */
 static void
-analyse_regex (regex_arg, ignore_case)
+analyse_regex (regex_arg)
      char *regex_arg;
-     bool ignore_case;
 {
   if (regex_arg == NULL)
     {
-      free_patterns ();                /* --no-regex: remove existing regexps */
+      free_regexps ();         /* --no-regex: remove existing regexps */
       return;
     }
 
@@ -5163,9 +5699,9 @@ analyse_regex (regex_arg, ignore_case)
            pfatal (regexfile);
            return;
          }
-       initbuffer (&regexbuf);
+       linebuffer_init (&regexbuf);
        while (readline_internal (&regexbuf, regexfp) > 0)
-         analyse_regex (regexbuf.buffer, ignore_case);
+         analyse_regex (regexbuf.buffer);
        free (regexbuf.buffer);
        fclose (regexfp);
       }
@@ -5184,69 +5720,136 @@ analyse_regex (regex_arg, ignore_case)
              error ("unterminated language name in regex: %s", regex_arg);
              return;
            }
-       *cp = '\0';
+       *cp++ = '\0';
        lang = get_language_from_langname (lang_name);
        if (lang == NULL)
          return;
-       add_regex (cp + 1, ignore_case, lang);
+       add_regex (cp, lang);
       }
       break;
 
       /* Regexp to be used for any language. */
     default:
-      add_regex (regex_arg, ignore_case, NULL);
+      add_regex (regex_arg, NULL);
       break;
     }
 }
 
-/* Turn a name, which is an ed-style (but Emacs syntax) regular
-   expression, into a real regular expression by compiling it. */
+/* Separate the regexp pattern, compile it,
+   and care for optional name and modifiers. */
 static void
-add_regex (regexp_pattern, ignore_case, lang)
+add_regex (regexp_pattern, lang)
      char *regexp_pattern;
-     bool ignore_case;
      language *lang;
 {
   static struct re_pattern_buffer zeropattern;
-  char *name;
+  char sep, *pat, *name, *modifiers;
   const char *err;
   struct re_pattern_buffer *patbuf;
-  pattern *pp;
+  regexp *rp;
+  bool
+    force_explicit_name = TRUE, /* do not use implicit tag names */
+    ignore_case = FALSE,       /* case is significant */
+    multi_line = FALSE,                /* matches are done one line at a time */
+    single_line = FALSE;       /* dot does not match newline */
 
 
-  if (regexp_pattern[strlen(regexp_pattern)-1] != regexp_pattern[0])
+  if (strlen(regexp_pattern) < 3)
     {
-      error ("%s: unterminated regexp", regexp_pattern);
+      error ("null regexp", (char *)NULL);
       return;
     }
+  sep = regexp_pattern[0];
   name = scan_separators (regexp_pattern);
-  if (regexp_pattern[0] == '\0')
+  if (name == NULL)
     {
-      error ("null regexp", (char *)NULL);
+      error ("%s: unterminated regexp", regexp_pattern);
       return;
     }
-  (void) scan_separators (name);
+  if (name[1] == sep)
+    {
+      error ("null name for regexp \"%s\"", regexp_pattern);
+      return;
+    }
+  modifiers = scan_separators (name);
+  if (modifiers == NULL)       /* no terminating separator --> no name */
+    {
+      modifiers = name;
+      name = "";
+    }
+  else
+    modifiers += 1;            /* skip separator */
+
+  /* Parse regex modifiers. */
+  for (; modifiers[0] != '\0'; modifiers++)
+    switch (modifiers[0])
+      {
+      case 'N':
+       if (modifiers == name)
+         error ("forcing explicit tag name but no name, ignoring", NULL);
+       force_explicit_name = TRUE;
+       break;
+      case 'i':
+       ignore_case = TRUE;
+       break;
+      case 's':
+       single_line = TRUE;
+       /* FALLTHRU */
+      case 'm':
+       multi_line = TRUE;
+       need_filebuf = TRUE;
+       break;
+      default:
+       {
+         char wrongmod [2];
+         wrongmod[0] = modifiers[0];
+         wrongmod[1] = '\0';
+         error ("invalid regexp modifier `%s', ignoring", wrongmod);
+       }
+       break;
+      }
 
   patbuf = xnew (1, struct re_pattern_buffer);
   *patbuf = zeropattern;
   if (ignore_case)
-    patbuf->translate = lc_trans;      /* translation table to fold case  */
+    {
+      static char lc_trans[CHARS];
+      int i;
+      for (i = 0; i < CHARS; i++)
+       lc_trans[i] = lowcase (i);
+      patbuf->translate = lc_trans;    /* translation table to fold case  */
+    }
 
-  err = re_compile_pattern (regexp_pattern, strlen (regexp_pattern), patbuf);
+  if (multi_line)
+    pat = concat ("^", regexp_pattern, ""); /* anchor to beginning of line */
+  else
+    pat = regexp_pattern;
+
+  if (single_line)
+    re_set_syntax (RE_SYNTAX_EMACS | RE_DOT_NEWLINE);
+  else
+    re_set_syntax (RE_SYNTAX_EMACS);
+
+  err = re_compile_pattern (pat, strlen (regexp_pattern), patbuf);
+  if (multi_line)
+    free (pat);
   if (err != NULL)
     {
       error ("%s while compiling pattern", err);
       return;
     }
 
-  pp = p_head;
-  p_head = xnew (1, pattern);
-  p_head->regex = savestr (regexp_pattern);
-  p_head->p_next = pp;
+  rp = p_head;
+  p_head = xnew (1, regexp);
+  p_head->pattern = savestr (regexp_pattern);
+  p_head->p_next = rp;
   p_head->lang = lang;
   p_head->pat = patbuf;
-  p_head->name_pattern = savestr (name);
+  p_head->name = savestr (name);
   p_head->error_signaled = FALSE;
+  p_head->force_explicit_name = force_explicit_name;
+  p_head->ignore_case = ignore_case;
+  p_head->multi_line = multi_line;
 }
 
 /*
@@ -5280,6 +5883,7 @@ substitute (in, out, regs)
       size -= 1;
 
   /* Allocate space and do the substitutions. */
+  assert (size >= 0);
   result = xnew (size + 1, char);
 
   for (t = result; *out != '\0'; out++)
@@ -5294,26 +5898,111 @@ substitute (in, out, regs)
       *t++ = *out;
   *t = '\0';
 
-  assert (t <= result + size && t - result == (int)strlen (result));
+  assert (t <= result + size);
+  assert (t - result == (int)strlen (result));
 
   return result;
 }
 
-/* Deallocate all patterns. */
+/* Deallocate all regexps. */
 static void
-free_patterns ()
+free_regexps ()
 {
-  pattern *pp;
+  regexp *rp;
   while (p_head != NULL)
     {
-      pp = p_head->p_next;
-      free (p_head->regex);
-      free (p_head->name_pattern);
+      rp = p_head->p_next;
+      free (p_head->pattern);
+      free (p_head->name);
       free (p_head);
-      p_head = pp;
+      p_head = rp;
     }
   return;
 }
+
+/*
+ * Reads the whole file as a single string from `filebuf' and looks for
+ * multi-line regular expressions, creating tags on matches.
+ * readline already dealt with normal regexps.
+ *
+ * Idea by Ben Wing <ben@666.com> (2002).
+ */
+static void
+regex_tag_multiline ()
+{
+  char *buffer = filebuf.buffer;
+  regexp *rp;
+  char *name;
+
+  for (rp = p_head; rp != NULL; rp = rp->p_next)
+    {
+      int match = 0;
+
+      if (!rp->multi_line)
+       continue;               /* skip normal regexps */
+
+      /* Generic initialisations before parsing file from memory. */
+      lineno = 1;              /* reset global line number */
+      charno = 0;              /* reset global char number */
+      linecharno = 0;          /* reset global char number of line start */
+
+      /* Only use generic regexps or those for the current language. */
+      if (rp->lang != NULL && rp->lang != curfdp->lang)
+       continue;
+
+      while (match >= 0 && match < filebuf.len)
+       {
+         match = re_search (rp->pat, buffer, filebuf.len, charno,
+                            filebuf.len - match, &rp->regs);
+         switch (match)
+           {
+           case -2:
+             /* Some error. */
+             if (!rp->error_signaled)
+               {
+                 error ("regexp stack overflow while matching \"%s\"",
+                        rp->pattern);
+                 rp->error_signaled = TRUE;
+               }
+             break;
+           case -1:
+             /* No match. */
+             break;
+           default:
+             if (match == rp->regs.end[0])
+               {
+                 if (!rp->error_signaled)
+                   {
+                     error ("regexp matches the empty string: \"%s\"",
+                            rp->pattern);
+                     rp->error_signaled = TRUE;
+                   }
+                 match = -3;   /* exit from while loop */
+                 break;
+               }
+
+             /* Match occurred.  Construct a tag. */
+             while (charno < rp->regs.end[0])
+               if (buffer[charno++] == '\n')
+                 lineno++, linecharno = charno;
+             name = rp->name;
+             if (name[0] == '\0')
+               name = NULL;
+             else /* make a named tag */
+               name = substitute (buffer, rp->name, &rp->regs);
+             if (rp->force_explicit_name)
+               /* Force explicit tag name, if a name is there. */
+               pfnote (name, TRUE, buffer + linecharno,
+                       charno - linecharno + 1, lineno, linecharno);
+             else
+               make_tag (name, strlen (name), TRUE, buffer + linecharno,
+                         charno - linecharno + 1, lineno, linecharno);
+             break;
+           }
+       }
+    }
+}
+
 #endif /* ETAGS_REGEXPS */
 
 \f
@@ -5333,32 +6022,24 @@ nocase_tail (cp)
   return FALSE;
 }
 
-static char *
-get_tag (bp)
+static void
+get_tag (bp, namepp)
      register char *bp;
+     char **namepp;
 {
-  register char *cp, *name;
+  register char *cp = bp;
 
-  if (*bp == '\0')
-    return NULL;
-  /* Go till you get to white space or a syntactic break */
-  for (cp = bp + 1; !notinname (*cp); cp++)
-    continue;
-  name = savenstr (bp, cp-bp);
-  pfnote (name, TRUE,
-         lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
-  return name;
-}
+  if (*bp != '\0')
+    {
+      /* Go till you get to white space or a syntactic break */
+      for (cp = bp + 1; !notinname (*cp); cp++)
+       continue;
+      make_tag (bp, cp - bp, TRUE,
+               lb.buffer, cp - lb.buffer + 1, lineno, linecharno);
+    }
 
-/* Initialize a linebuffer for use */
-static void
-initbuffer (lbp)
-     linebuffer *lbp;
-{
-  lbp->size = (DEBUG) ? 3 : 200;
-  lbp->buffer = xnew (lbp->size, char);
-  lbp->buffer[0] = '\0';
-  lbp->len = 0;
+  if (namepp != NULL)
+    *namepp = savenstr (bp, cp - bp);
 }
 
 /*
@@ -5366,10 +6047,13 @@ initbuffer (lbp)
  * newline or CR-NL, if any.  Return the number of characters read from
  * `stream', which is the length of the line including the newline.
  *
- * On DOS or Windows we do not count the CR character, if any, before the
- * NL, in the returned length; this mirrors the behavior of emacs on those
+ * On DOS or Windows we do not count the CR character, if any before the
+ * NL, in the returned length; this mirrors the behavior of Emacs on those
  * platforms (for text files, it translates CR-NL to NL as it reads in the
  * file).
+ *
+ * If multi-line regular expressions are requested, each line read is
+ * appended to `filebuf'.
  */
 static long
 readline_internal (lbp, stream)
@@ -5383,7 +6067,7 @@ readline_internal (lbp, stream)
 
   pend = p + lbp->size;                /* Separate to avoid 386/IX compiler bug.  */
 
-  while (1)
+  for (;;)
     {
       register int c = getc (stream);
       if (p == pend)
@@ -5428,111 +6112,219 @@ readline_internal (lbp, stream)
     }
   lbp->len = p - buffer;
 
+  if (need_filebuf             /* we need filebuf for multi-line regexps */
+      && chars_deleted > 0)    /* not at EOF */
+    {
+      while (filebuf.size <= filebuf.len + lbp->len + 1) /* +1 for \n */
+       {
+         /* Expand filebuf. */
+         filebuf.size *= 2;
+         xrnew (filebuf.buffer, filebuf.size, char);
+       }
+      strncpy (filebuf.buffer + filebuf.len, lbp->buffer, lbp->len);
+      filebuf.len += lbp->len;
+      filebuf.buffer[filebuf.len++] = '\n';
+      filebuf.buffer[filebuf.len] = '\0';
+    }
+
   return lbp->len + chars_deleted;
 }
 
 /*
  * Like readline_internal, above, but in addition try to match the
- * input line against relevant regular expressions.
+ * input line against relevant regular expressions and manage #line
+ * directives.
  */
-static long
+static void
 readline (lbp, stream)
      linebuffer *lbp;
      FILE *stream;
 {
-  /* Read new line. */
-  long result = readline_internal (lbp, stream);
+  long result;
+
+  linecharno = charno;         /* update global char number of line start */
+  result = readline_internal (lbp, stream); /* read line */
+  lineno += 1;                 /* increment global line number */
+  charno += result;            /* increment global char number */
 
   /* Honour #line directives. */
-  if (!no_line_directive
-      && result > 12 && strneq (lbp->buffer, "#line ", 6))
+  if (!no_line_directive)
     {
-      int start, lno;
+      static bool discard_until_line_directive;
 
-      if (sscanf (lbp->buffer, "#line %d \"%n", &lno, &start) == 1)
+      /* Check whether this is a #line directive. */
+      if (result > 12 && strneq (lbp->buffer, "#line ", 6))
        {
-         char *endp = lbp->buffer + start;
+         int start, lno;
 
-         while ((endp = etags_strchr (endp, '"')) != NULL
-                && endp[-1] == '\\')
-           endp++;
-         if (endp != NULL)
+         if (DEBUG) start = 0; /* shut up the compiler */
+         if (sscanf (lbp->buffer, "#line %d \"%n", &lno, &start) == 1)
            {
-             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
+             char *endp = lbp->buffer + start;
+
+             assert (start > 0);
+             while ((endp = etags_strchr (endp, '"')) != NULL
+                    && endp[-1] == '\\')
+               endp++;
+             if (endp != NULL)
+               /* Ok, this is a real #line directive.  Let's deal with it. */
                {
-                 name = relative_filename (absname, tagfiledir);
-                 free (absname);
-               }
+                 char *taggedabsname;  /* absolute name of original file */
+                 char *taggedfname;    /* name of original file as given */
+                 char *name;           /* temp var */
+
+                 discard_until_line_directive = FALSE; /* found it */
+                 name = lbp->buffer + start;
+                 *endp = '\0';
+                 canonicalize_filename (name); /* for DOS */
+                 taggedabsname = absolute_filename (name, curfdp->infabsdir);
+                 if (filename_is_absolute (name)
+                     || filename_is_absolute (curfdp->infname))
+                   taggedfname = savestr (taggedabsname);
+                 else
+                   taggedfname = relative_filename (taggedabsname,tagfiledir);
 
-             if (streq (curtagfname, name))
-               free (name);
-             else
-               curtagfname = name;
-             lineno = lno;
-             nocharno = TRUE;  /* do not use char position for tags */
-             return readline (lbp, stream);
+                 if (streq (curfdp->taggedfname, taggedfname))
+                   /* The #line directive is only a line number change.  We
+                      deal with this afterwards. */
+                   free (taggedfname);
+                 else
+                   /* The tags following this #line directive should be
+                      attributed to taggedfname.  In order to do this, set
+                      curfdp accordingly. */
+                   {
+                     fdesc *fdp; /* file description pointer */
+
+                     /* Go look for a file description already set up for the
+                        file indicated in the #line directive.  If there is
+                        one, use it from now until the next #line
+                        directive. */
+                     for (fdp = fdhead; fdp != NULL; fdp = fdp->next)
+                       if (streq (fdp->infname, curfdp->infname)
+                           && streq (fdp->taggedfname, taggedfname))
+                         /* If we remove the second test above (after the &&)
+                            then all entries pertaining to the same file are
+                            coalesced in the tags file.  If we use it, then
+                            entries pertaining to the same file but generated
+                            from different files (via #line directives) will
+                            go into separate sections in the tags file.  These
+                            alternatives look equivalent.  The first one
+                            destroys some apparently useless information. */
+                         {
+                           curfdp = fdp;
+                           free (taggedfname);
+                           break;
+                         }
+                     /* Else, if we already tagged the real file, skip all
+                        input lines until the next #line directive. */
+                     if (fdp == NULL) /* not found */
+                       for (fdp = fdhead; fdp != NULL; fdp = fdp->next)
+                         if (streq (fdp->infabsname, taggedabsname))
+                           {
+                             discard_until_line_directive = TRUE;
+                             free (taggedfname);
+                             break;
+                           }
+                     /* Else create a new file description and use that from
+                        now on, until the next #line directive. */
+                     if (fdp == NULL) /* not found */
+                       {
+                         fdp = fdhead;
+                         fdhead = xnew (1, fdesc);
+                         *fdhead = *curfdp; /* copy curr. file description */
+                         fdhead->next = fdp;
+                         fdhead->infname = savestr (curfdp->infname);
+                         fdhead->infabsname = savestr (curfdp->infabsname);
+                         fdhead->infabsdir = savestr (curfdp->infabsdir);
+                         fdhead->taggedfname = taggedfname;
+                         fdhead->usecharno = FALSE;
+                         fdhead->prop = NULL;
+                         fdhead->written = FALSE;
+                         curfdp = fdhead;
+                       }
+                   }
+                 free (taggedabsname);
+                 lineno = lno - 1;
+                 readline (lbp, stream);
+                 return;
+               } /* if a real #line directive */
+           } /* if #line is followed by a a number */
+       } /* if line begins with "#line " */
+
+      /* If we are here, no #line directive was found. */
+      if (discard_until_line_directive)
+       {
+         if (result > 0)
+           {
+             /* Do a tail recursion on ourselves, thus discarding the contents
+                of the line buffer. */
+             readline (lbp, stream);
+             return;
            }
+         /* End of file. */
+         discard_until_line_directive = FALSE;
+         return;
        }
-    }
+    } /* if #line directives should be considered */
 
 #ifdef ETAGS_REGEXPS
   {
     int match;
-    pattern *pp;
+    regexp *rp;
+    char *name;
 
-    /* Match against relevant patterns. */
+    /* Match against relevant regexps. */
     if (lbp->len > 0)
-      for (pp = p_head; pp != NULL; pp = pp->p_next)
+      for (rp = p_head; rp != NULL; rp = rp->p_next)
        {
-         /* Only use generic regexps or those for the current language. */
-         if (pp->lang != NULL && pp->lang != curlang)
+         /* Only use generic regexps or those for the current language.
+            Also do not use multiline regexps, which is the job of
+            regex_tag_multiline. */
+         if ((rp->lang != NULL && rp->lang != fdhead->lang)
+             || rp->multi_line)
            continue;
 
-         match = re_match (pp->pat, lbp->buffer, lbp->len, 0, &pp->regs);
+         match = re_match (rp->pat, lbp->buffer, lbp->len, 0, &rp->regs);
          switch (match)
            {
            case -2:
              /* Some error. */
-             if (!pp->error_signaled)
+             if (!rp->error_signaled)
                {
-                 error ("error while matching \"%s\"", pp->regex);
-                 pp->error_signaled = TRUE;
+                 error ("regexp stack overflow while matching \"%s\"",
+                        rp->pattern);
+                 rp->error_signaled = TRUE;
                }
              break;
            case -1:
              /* No match. */
              break;
-           default:
-             /* Match occurred.  Construct a tag. */
-             if (pp->name_pattern[0] != '\0')
+           case 0:
+             /* Empty string matched. */
+             if (!rp->error_signaled)
                {
-                 /* 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);
+                 error ("regexp matches the empty string: \"%s\"", rp->pattern);
+                 rp->error_signaled = TRUE;
                }
+             break;
+           default:
+             /* Match occurred.  Construct a tag. */
+             name = rp->name;
+             if (name[0] == '\0')
+               name = NULL;
+             else /* make a named tag */
+               name = substitute (lbp->buffer, rp->name, &rp->regs);
+             if (rp->force_explicit_name)
+               /* Force explicit tag name, if a name is there. */
+               pfnote (name, TRUE, lbp->buffer, match, lineno, linecharno);
              else
-               {
-                 /* Make an unnamed tag. */
-                 pfnote ((char *)NULL, TRUE,
+               make_tag (name, strlen (name), TRUE,
                          lbp->buffer, match, lineno, linecharno);
-               }
              break;
            }
        }
   }
 #endif /* ETAGS_REGEXPS */
-
-  return result;
 }
 
 \f
@@ -5586,7 +6378,6 @@ etags_strrchr (sp, c)
   return (char *)r;
 }
 
-
 /*
  * Return the ptr in sp at which the character c first
  * appears; NULL if not found
@@ -5606,6 +6397,53 @@ etags_strchr (sp, c)
   return NULL;
 }
 
+/*
+ * Compare two strings, ignoring case for alphabetic characters.
+ *
+ * Same as BSD's strcasecmp, included for portability.
+ */
+static int
+etags_strcasecmp (s1, s2)
+     register const char *s1;
+     register const char *s2;
+{
+  while (*s1 != '\0'
+        && (ISALPHA (*s1) && ISALPHA (*s2)
+            ? lowcase (*s1) == lowcase (*s2)
+            : *s1 == *s2))
+    s1++, s2++;
+
+  return (ISALPHA (*s1) && ISALPHA (*s2)
+         ? lowcase (*s1) - lowcase (*s2)
+         : *s1 - *s2);
+}
+
+/*
+ * Compare two strings, ignoring case for alphabetic characters.
+ * Stop after a given number of characters
+ *
+ * Same as BSD's strncasecmp, included for portability.
+ */
+static int
+etags_strncasecmp (s1, s2, n)
+     register const char *s1;
+     register const char *s2;
+     register int n;
+{
+  while (*s1 != '\0' && n-- > 0
+        && (ISALPHA (*s1) && ISALPHA (*s2)
+            ? lowcase (*s1) == lowcase (*s2)
+            : *s1 == *s2))
+    s1++, s2++;
+
+  if (n < 0)
+    return 0;
+  else
+    return (ISALPHA (*s1) && ISALPHA (*s2)
+           ? lowcase (*s1) - lowcase (*s2)
+           : *s1 - *s2);
+}
+
 /* Skip spaces, return new pointer. */
 static char *
 skip_spaces (cp)
@@ -5646,14 +6484,14 @@ pfatal (s1)
 static void
 suggest_asking_for_help ()
 {
-  fprintf (stderr, "\tTry `%s %s' for a complete list of options.\n",
-          progname,
+
 #ifdef LONG_OPTIONS
-          "--help"
+fprintf (stderr, "\tTry `%s %s' for a complete list of options.\n",
+        progname, "--help");
 #else
-          "-h"
+fprintf (stderr, "\tTry `%s %s' for a complete list of options.\n",
+        progname, "-h");
 #endif
-          );
   exit (BAD);
 }
 
@@ -5724,7 +6562,7 @@ etags_getcwd ()
   linebuffer path;
   FILE *pipe;
 
-  initbuffer (&path);
+  linebuffer_init (&path);
   pipe = (FILE *) popen ("pwd 2>/dev/null", "r");
   if (pipe == NULL || readline_internal (&path, pipe) == 0)
     pfatal ("pwd");
@@ -5890,6 +6728,18 @@ canonicalize_filename (fn)
 #endif
 }
 
+\f
+/* Initialize a linebuffer for use */
+static void
+linebuffer_init (lbp)
+     linebuffer *lbp;
+{
+  lbp->size = (DEBUG) ? 3 : 200;
+  lbp->buffer = xnew (lbp->size, char);
+  lbp->buffer[0] = '\0';
+  lbp->len = 0;
+}
+
 /* Set the minimum size of a string contained in a linebuffer. */
 static void
 linebuffer_setlen (lbp, toksize)
@@ -5904,7 +6754,7 @@ linebuffer_setlen (lbp, toksize)
   lbp->len = toksize;
 }
 
-/* Like malloc but get fatal error if memory is exhausted.  */
+/* Like malloc but get fatal error if memory is exhausted. */
 static PTR
 xmalloc (size)
      unsigned int size;
@@ -5931,6 +6781,10 @@ xrealloc (ptr, size)
  * c-indentation-style: gnu
  * indent-tabs-mode: t
  * tab-width: 8
- * c-font-lock-extra-types: ("FILE" "bool" "language" "linebuffer")
+ * fill-column: 79
+ * c-font-lock-extra-types: ("FILE" "bool" "language" "linebuffer" "fdesc" "node" "regexp")
  * End:
  */
+
+/* arch-tag: 8a9b748d-390c-4922-99db-2eeefa921051
+   (do not change this comment) */