Prefer plain 'static' to 'static inline'.
[bpt/emacs.git] / lib-src / make-docfile.c
index 9d9c669..9bc91bc 100644 (file)
@@ -1,7 +1,7 @@
 /* Generate doc-string file for GNU Emacs from source files.
-   Copyright (C) 1985, 1986, 1992, 1993, 1994, 1997, 1999, 2000, 2001,
-                 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
-                 Free Software Foundation, Inc.
+
+Copyright (C) 1985-1986, 1992-1994, 1997, 1999-2013 Free Software
+Foundation, Inc.
 
 This file is part of GNU Emacs.
 
@@ -36,50 +36,42 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include <config.h>
 
-/* defined to be emacs_main, sys_fopen, etc. in config.h */
-#undef main
-#undef fopen
-#undef chdir
-
 #include <stdio.h>
+#include <stdlib.h>   /* config.h unconditionally includes this anyway */
 #ifdef MSDOS
 #include <fcntl.h>
 #endif /* MSDOS */
 #ifdef WINDOWSNT
-#include <stdlib.h>
+/* Defined to be sys_fopen in ms-w32.h, but only #ifdef emacs, so this
+   is really just insurance.  */
+#undef fopen
 #include <fcntl.h>
 #include <direct.h>
 #endif /* WINDOWSNT */
 
 #ifdef DOS_NT
+/* Defined to be sys_chdir in ms-w32.h, but only #ifdef emacs, so this
+   is really just insurance.
+
+   Similarly, msdos defines this as sys_chdir, but we're not linking with the
+   file where that function is defined.  */
+#undef chdir
 #define READ_TEXT "rt"
 #define READ_BINARY "rb"
+#define IS_SLASH(c)  ((c) == '/' || (c) == '\\' || (c) == ':')
 #else  /* not DOS_NT */
 #define READ_TEXT "r"
 #define READ_BINARY "r"
+#define IS_SLASH(c)  ((c) == '/')
 #endif /* not DOS_NT */
 
-#ifndef DIRECTORY_SEP
-#define DIRECTORY_SEP '/'
-#endif
-
-#ifndef IS_DIRECTORY_SEP
-#define IS_DIRECTORY_SEP(_c_) ((_c_) == DIRECTORY_SEP)
-#endif
-
-int scan_file ();
-int scan_lisp_file ();
-int scan_c_file ();
-
-#ifdef MSDOS
-/* s/msdos.h defines this as sys_chdir, but we're not linking with the
-   file where that function is defined.  */
-#undef chdir
-#endif
+static int scan_file (char *filename);
+static int scan_lisp_file (const char *filename, const char *mode);
+static int scan_c_file (char *filename, const char *mode);
+static void start_globals (void);
+static void write_globals (void);
 
-#ifdef HAVE_UNISTD_H
 #include <unistd.h>
-#endif
 
 /* Stdio stream for output to the DOC file.  */
 FILE *outfile;
@@ -87,12 +79,14 @@ FILE *outfile;
 /* Name this program was invoked with.  */
 char *progname;
 
+/* Nonzero if this invocation is generating globals.h.  */
+int generate_globals;
+
 /* Print error message.  `s1' is printf control string, `s2' is arg for it.  */
 
 /* VARARGS1 */
-void
-error (s1, s2)
-     char *s1, *s2;
+static void
+error (const char *s1, const char *s2)
 {
   fprintf (stderr, "%s: ", progname);
   fprintf (stderr, s1, s2);
@@ -102,9 +96,8 @@ error (s1, s2)
 /* Print error message and exit.  */
 
 /* VARARGS1 */
-void
-fatal (s1, s2)
-     char *s1, *s2;
+static _Noreturn void
+fatal (const char *s1, const char *s2)
 {
   error (s1, s2);
   exit (EXIT_FAILURE);
@@ -112,20 +105,29 @@ fatal (s1, s2)
 
 /* Like malloc but get fatal error if memory is exhausted.  */
 
-void *
-xmalloc (size)
-     unsigned int size;
+static void *
+xmalloc (unsigned int size)
 {
   void *result = (void *) malloc (size);
   if (result == NULL)
     fatal ("virtual memory exhausted", 0);
   return result;
 }
+
+/* Like realloc but get fatal error if memory is exhausted.  */
+
+static void *
+xrealloc (void *arg, unsigned int size)
+{
+  void *result = (void *) realloc (arg, size);
+  if (result == NULL)
+    fatal ("virtual memory exhausted", 0);
+  return result;
+}
+
 \f
 int
-main (argc, argv)
-     int argc;
-     char **argv;
+main (int argc, char **argv)
 {
   int i;
   int err_count = 0;
@@ -164,13 +166,25 @@ main (argc, argv)
     }
   if (argc > i + 1 && !strcmp (argv[i], "-d"))
     {
-      chdir (argv[i + 1]);
+      if (chdir (argv[i + 1]) != 0)
+       {
+         perror (argv[i + 1]);
+         return EXIT_FAILURE;
+       }
       i += 2;
     }
+  if (argc > i && !strcmp (argv[i], "-g"))
+    {
+      generate_globals = 1;
+      ++i;
+    }
 
   if (outfile == 0)
     fatal ("No output file specified", "");
 
+  if (generate_globals)
+    start_globals ();
+
   first_infile = i;
   for (; i < argc; i++)
     {
@@ -182,19 +196,22 @@ main (argc, argv)
       if (j == i)
        err_count += scan_file (argv[i]);
     }
+
+  if (err_count == 0 && generate_globals)
+    write_globals ();
+
   return (err_count > 0 ? EXIT_FAILURE : EXIT_SUCCESS);
 }
 
 /* Add a source file name boundary marker in the output file.  */
-void
-put_filename (filename)
-     char *filename;
+static void
+put_filename (char *filename)
 {
   char *tmp;
 
   for (tmp = filename; *tmp; tmp++)
     {
-      if (IS_DIRECTORY_SEP(*tmp))
+      if (IS_DIRECTORY_SEP (*tmp))
        filename = tmp + 1;
     }
 
@@ -206,13 +223,14 @@ put_filename (filename)
 /* Read file FILENAME and output its doc strings to outfile.  */
 /* Return 1 if file is not found, 0 if it is found.  */
 
-int
-scan_file (filename)
-     char *filename;
+static int
+scan_file (char *filename)
 {
-  int len = strlen (filename);
 
-  put_filename (filename);
+  size_t len = strlen (filename);
+
+  if (!generate_globals)
+    put_filename (filename);
   if (len > 4 && !strcmp (filename + len - 4, ".elc"))
     return scan_lisp_file (filename, READ_BINARY);
   else if (len > 3 && !strcmp (filename + len - 3, ".el"))
@@ -220,8 +238,16 @@ scan_file (filename)
   else
     return scan_c_file (filename, READ_TEXT);
 }
+
+static void
+start_globals (void)
+{
+  fprintf (outfile, "/* This file was auto-generated by make-docfile.  */\n");
+  fprintf (outfile, "/* DO NOT EDIT.  */\n");
+  fprintf (outfile, "struct emacs_globals {\n");
+}
 \f
-char buf[128];
+static char input_buffer[128];
 
 /* Some state during the execution of `read_c_string_or_comment'.  */
 struct rcsoc_state
@@ -239,10 +265,10 @@ struct rcsoc_state
 
   /* A keyword we look for at the beginning of lines.  If found, it is
      not copied, and SAW_KEYWORD is set to true.  */
-  char *keyword;
+  const char *keyword;
   /* The current point we've reached in an occurrence of KEYWORD in
      the input stream.  */
-  char *cur_keyword_ptr;
+  const char *cur_keyword_ptr;
   /* Set to true if we saw an occurrence of KEYWORD.  */
   int saw_keyword;
 };
@@ -250,10 +276,8 @@ struct rcsoc_state
 /* Output CH to the file or buffer in STATE.  Any pending newlines or
    spaces are output first.  */
 
-static INLINE void
-put_char (ch, state)
-     int ch;
-     struct rcsoc_state *state;
+static void
+put_char (int ch, struct rcsoc_state *state)
 {
   int out_ch;
   do
@@ -286,9 +310,7 @@ put_char (ch, state)
    keyword, but were in fact not.  */
 
 static void
-scan_keyword_or_put_char (ch, state)
-     int ch;
-     struct rcsoc_state *state;
+scan_keyword_or_put_char (int ch, struct rcsoc_state *state)
 {
   if (state->keyword
       && *state->cur_keyword_ptr == ch
@@ -336,7 +358,7 @@ scan_keyword_or_put_char (ch, state)
           keyword, but it was a false alarm.  Output the
           part we scanned.  */
        {
-         char *p;
+         const char *p;
 
          for (p = state->keyword; p < state->cur_keyword_ptr; p++)
            put_char (*p, state);
@@ -358,18 +380,14 @@ scan_keyword_or_put_char (ch, state)
    at the beginning of a line will be removed, and *SAW_USAGE set to
    true if any were encountered.  */
 
-int
-read_c_string_or_comment (infile, printflag, comment, saw_usage)
-     FILE *infile;
-     int printflag;
-     int *saw_usage;
-     int comment;
+static int
+read_c_string_or_comment (FILE *infile, int printflag, int comment, int *saw_usage)
 {
   register int c;
   struct rcsoc_state state;
 
   state.in_file = infile;
-  state.buf_ptr = (printflag < 0 ? buf : 0);
+  state.buf_ptr = (printflag < 0 ? input_buffer : 0);
   state.out_file = (printflag > 0 ? outfile : 0);
   state.pending_spaces = 0;
   state.pending_newlines = 0;
@@ -450,16 +468,13 @@ read_c_string_or_comment (infile, printflag, comment, saw_usage)
 /* Write to file OUT the argument names of function FUNC, whose text is in BUF.
    MINARGS and MAXARGS are the minimum and maximum number of arguments.  */
 
-void
-write_c_args (out, func, buf, minargs, maxargs)
-     FILE *out;
-     char *func, *buf;
-     int minargs, maxargs;
+static void
+write_c_args (FILE *out, char *func, char *buf, int minargs, int maxargs)
 {
   register char *p;
   int in_ident = 0;
-  int just_spaced = 0;
-  int need_space = 1;
+  char *ident_start IF_LINT (= NULL);
+  size_t ident_length = 0;
 
   fprintf (out, "(fn");
 
@@ -469,9 +484,8 @@ write_c_args (out, func, buf, minargs, maxargs)
   for (p = buf; *p; p++)
     {
       char c = *p;
-      int ident_start = 0;
 
-      /* Notice when we start printing a new identifier.  */
+      /* Notice when a new identifier starts.  */
       if ((('A' <= c && c <= 'Z')
           || ('a' <= c && c <= 'z')
           || ('0' <= c && c <= '9')
@@ -481,72 +495,213 @@ write_c_args (out, func, buf, minargs, maxargs)
          if (!in_ident)
            {
              in_ident = 1;
-             ident_start = 1;
+             ident_start = p;
+           }
+         else
+           {
+             in_ident = 0;
+             ident_length = p - ident_start;
+           }
+       }
 
-             if (need_space)
-               putc (' ', out);
+      /* Found the end of an argument, write out the last seen
+        identifier.  */
+      if (c == ',' || c == ')')
+       {
+         if (ident_length == 0)
+           {
+             error ("empty arg list for `%s' should be (void), not ()", func);
+             continue;
+           }
 
-             if (minargs == 0 && maxargs > 0)
-               fprintf (out, "&optional ");
-             just_spaced = 1;
+         if (strncmp (ident_start, "void", ident_length) == 0)
+           continue;
 
-             minargs--;
-             maxargs--;
-           }
+         putc (' ', out);
+
+         if (minargs == 0 && maxargs > 0)
+           fprintf (out, "&optional ");
+
+         minargs--;
+         maxargs--;
+
+         /* In C code, `default' is a reserved word, so we spell it
+            `defalt'; demangle that here.  */
+         if (ident_length == 6 && memcmp (ident_start, "defalt", 6) == 0)
+           fprintf (out, "DEFAULT");
          else
-           in_ident = 0;
+           while (ident_length-- > 0)
+             {
+               c = *ident_start++;
+               if (c >= 'a' && c <= 'z')
+                 /* Upcase the letter.  */
+                 c += 'A' - 'a';
+               else if (c == '_')
+                 /* Print underscore as hyphen.  */
+                 c = '-';
+               putc (c, out);
+             }
+       }
+    }
+
+  putc (')', out);
+}
+\f
+/* The types of globals.  These are sorted roughly in decreasing alignment
+   order to avoid allocation gaps, except that functions are last.  */
+enum global_type
+{
+  INVALID,
+  LISP_OBJECT,
+  EMACS_INTEGER,
+  BOOLEAN,
+  FUNCTION,
+};
+
+/* A single global.  */
+struct global
+{
+  enum global_type type;
+  char *name;
+  int value;
+};
+
+/* All the variable names we saw while scanning C sources in `-g'
+   mode.  */
+int num_globals;
+int num_globals_allocated;
+struct global *globals;
+
+static void
+add_global (enum global_type type, char *name, int value)
+{
+  /* Ignore the one non-symbol that can occur.  */
+  if (strcmp (name, "..."))
+    {
+      ++num_globals;
+
+      if (num_globals_allocated == 0)
+       {
+         num_globals_allocated = 100;
+         globals = xmalloc (num_globals_allocated * sizeof (struct global));
+       }
+      else if (num_globals == num_globals_allocated)
+       {
+         num_globals_allocated *= 2;
+         globals = xrealloc (globals,
+                             num_globals_allocated * sizeof (struct global));
+       }
+
+      globals[num_globals - 1].type = type;
+      globals[num_globals - 1].name = name;
+      globals[num_globals - 1].value = value;
+    }
+}
+
+static int
+compare_globals (const void *a, const void *b)
+{
+  const struct global *ga = a;
+  const struct global *gb = b;
+
+  if (ga->type != gb->type)
+    return ga->type - gb->type;
+
+  return strcmp (ga->name, gb->name);
+}
+
+static void
+close_emacs_globals (void)
+{
+  fprintf (outfile, "};\n");
+  fprintf (outfile, "extern struct emacs_globals globals;\n");
+}
+
+static void
+write_globals (void)
+{
+  int i, seen_defun = 0;
+  qsort (globals, num_globals, sizeof (struct global), compare_globals);
+  for (i = 0; i < num_globals; ++i)
+    {
+      char const *type = 0;
+
+      switch (globals[i].type)
+       {
+       case EMACS_INTEGER:
+         type = "EMACS_INT";
+         break;
+       case BOOLEAN:
+         type = "bool";
+         break;
+       case LISP_OBJECT:
+         type = "Lisp_Object";
+         break;
+       case FUNCTION:
+         if (!seen_defun)
+           {
+             close_emacs_globals ();
+             fprintf (outfile, "\n");
+             seen_defun = 1;
+           }
+         break;
+       default:
+         fatal ("not a recognized DEFVAR_", 0);
        }
 
-      /* Print the C argument list as it would appear in lisp:
-        print underscores as hyphens, and print commas and newlines
-        as spaces.  Collapse adjacent spaces into one.  */
-      if (c == '_')
-       c = '-';
-      else if (c == ',' || c == '\n')
-       c = ' ';
-
-      /* In C code, `default' is a reserved word, so we spell it
-        `defalt'; unmangle that here.  */
-      if (ident_start
-         && strncmp (p, "defalt", 6) == 0
-         && ! (('A' <= p[6] && p[6] <= 'Z')
-               || ('a' <= p[6] && p[6] <= 'z')
-               || ('0' <= p[6] && p[6] <= '9')
-               || p[6] == '_'))
+      if (type)
        {
-         fprintf (out, "DEFAULT");
-         p += 5;
-         in_ident = 0;
-         just_spaced = 0;
+         fprintf (outfile, "  %s f_%s;\n", type, globals[i].name);
+         fprintf (outfile, "#define %s globals.f_%s\n",
+                  globals[i].name, globals[i].name);
        }
-      else if (c != ' ' || !just_spaced)
+      else
        {
-         if (c >= 'a' && c <= 'z')
-           /* Upcase the letter.  */
-           c += 'A' - 'a';
-         putc (c, out);
+         /* It would be nice to have a cleaner way to deal with these
+            special hacks.  */
+         if (strcmp (globals[i].name, "Fthrow") == 0
+             || strcmp (globals[i].name, "Ftop_level") == 0
+             || strcmp (globals[i].name, "Fkill_emacs") == 0
+             || strcmp (globals[i].name, "Fexit_recursive_edit") == 0
+             || strcmp (globals[i].name, "Fabort_recursive_edit") == 0)
+           fprintf (outfile, "_Noreturn ");
+         fprintf (outfile, "EXFUN (%s, ", globals[i].name);
+         if (globals[i].value == -1)
+           fprintf (outfile, "MANY");
+         else if (globals[i].value == -2)
+           fprintf (outfile, "UNEVALLED");
+         else
+           fprintf (outfile, "%d", globals[i].value);
+         fprintf (outfile, ");\n");
        }
 
-      just_spaced = c == ' ';
-      need_space = 0;
+      while (i + 1 < num_globals
+            && !strcmp (globals[i].name, globals[i + 1].name))
+       {
+         if (globals[i].type == FUNCTION
+             && globals[i].value != globals[i + 1].value)
+           error ("function '%s' defined twice with differing signatures",
+                  globals[i].name);
+         ++i;
+       }
     }
+
+  if (!seen_defun)
+    close_emacs_globals ();
 }
+
 \f
 /* Read through a c file.  If a .o file is named,
    the corresponding .c or .m file is read instead.
    Looks for DEFUN constructs such as are defined in ../src/lisp.h.
    Accepts any word starting DEF... so it finds DEFSIMPLE and DEFPRED.  */
 
-int
-scan_c_file (filename, mode)
-     char *filename, *mode;
+static int
+scan_c_file (char *filename, const char *mode)
 {
   FILE *infile;
   register int c;
   register int commas;
-  register int defunflag;
-  register int defvarperbufferflag;
-  register int defvarflag;
   int minargs, maxargs;
   int extension = filename[strlen (filename) - 1];
 
@@ -557,14 +712,14 @@ scan_c_file (filename, mode)
 
   if (infile == NULL && extension == 'o')
     {
-      /* try .m */
+      /* Try .m.  */
       filename[strlen (filename) - 1] = 'm';
       infile = fopen (filename, mode);
       if (infile == NULL)
-        filename[strlen (filename) - 1] = 'c'; /* don't confuse people */
+        filename[strlen (filename) - 1] = 'c'; /* Don't confuse people.  */
     }
 
-  /* No error if non-ex input file */
+  /* No error if non-ex input file */
   if (infile == NULL)
     {
       perror (filename);
@@ -578,6 +733,11 @@ scan_c_file (filename, mode)
   while (!feof (infile))
     {
       int doc_keyword = 0;
+      int defunflag = 0;
+      int defvarperbufferflag = 0;
+      int defvarflag = 0;
+      enum global_type type = INVALID;
+      char *name IF_LINT (= 0);
 
       if (c != '\n' && c != '\r')
        {
@@ -611,12 +771,24 @@ scan_c_file (filename, mode)
            continue;
 
          defvarflag = 1;
-         defunflag = 0;
 
          c = getc (infile);
          defvarperbufferflag = (c == 'P');
+         if (generate_globals)
+           {
+             if (c == 'I')
+               type = EMACS_INTEGER;
+             else if (c == 'L')
+               type = LISP_OBJECT;
+             else if (c == 'B')
+               type = BOOLEAN;
+           }
 
          c = getc (infile);
+         /* We need to distinguish between DEFVAR_BOOL and
+            DEFVAR_BUFFER_DEFAULTS.  */
+         if (generate_globals && type == BOOLEAN && c != 'O')
+           type = INVALID;
        }
       else if (c == 'D')
        {
@@ -628,11 +800,14 @@ scan_c_file (filename, mode)
            continue;
          c = getc (infile);
          defunflag = c == 'U';
-         defvarflag = 0;
-         defvarperbufferflag = 0;
        }
       else continue;
 
+      if (generate_globals
+         && (!defvarflag || defvarperbufferflag || type == INVALID)
+         && !defunflag)
+       continue;
+
       while (c != '(')
        {
          if (c < 0)
@@ -646,17 +821,48 @@ scan_c_file (filename, mode)
        continue;
       c = read_c_string_or_comment (infile, -1, 0, 0);
 
+      if (generate_globals)
+       {
+         int i = 0;
+
+         /* Skip "," and whitespace.  */
+         do
+           {
+             c = getc (infile);
+           }
+         while (c == ',' || c == ' ' || c == '\t' || c == '\n' || c == '\r');
+
+         /* Read in the identifier.  */
+         do
+           {
+             input_buffer[i++] = c;
+             c = getc (infile);
+           }
+         while (! (c == ',' || c == ' ' || c == '\t'
+                   || c == '\n' || c == '\r'));
+         input_buffer[i] = '\0';
+
+         name = xmalloc (i + 1);
+         memcpy (name, input_buffer, i + 1);
+
+         if (!defunflag)
+           {
+             add_global (type, name, 0);
+             continue;
+           }
+       }
+
       /* DEFVAR_LISP ("name", addr, "doc")
         DEFVAR_LISP ("name", addr /\* doc *\/)
         DEFVAR_LISP ("name", addr, doc: /\* doc *\/)  */
 
       if (defunflag)
-       commas = 5;
+       commas = generate_globals ? 4 : 5;
       else if (defvarperbufferflag)
-       commas = 2;
+       commas = 3;
       else if (defvarflag)
        commas = 1;
-      else  /* For DEFSIMPLE and DEFPRED */
+      else  /* For DEFSIMPLE and DEFPRED */
        commas = 2;
 
       while (commas)
@@ -667,19 +873,27 @@ scan_c_file (filename, mode)
 
              if (defunflag && (commas == 1 || commas == 2))
                {
+                 int scanned = 0;
                  do
                    c = getc (infile);
                  while (c == ' ' || c == '\n' || c == '\r' || c == '\t');
                  if (c < 0)
                    goto eof;
                  ungetc (c, infile);
-                 if (commas == 2) /* pick up minargs */
-                   fscanf (infile, "%d", &minargs);
-                 else /* pick up maxargs */
+                 if (commas == 2) /* Pick up minargs.  */
+                   scanned = fscanf (infile, "%d", &minargs);
+                 else /* Pick up maxargs.  */
                    if (c == 'M' || c == 'U') /* MANY || UNEVALLED */
-                     maxargs = -1;
+                     {
+                       if (generate_globals)
+                         maxargs = (c == 'M') ? -1 : -2;
+                       else
+                         maxargs = -1;
+                     }
                    else
-                     fscanf (infile, "%d", &maxargs);
+                     scanned = fscanf (infile, "%d", &maxargs);
+                 if (scanned < 0)
+                   goto eof;
                }
            }
 
@@ -688,6 +902,12 @@ scan_c_file (filename, mode)
          c = getc (infile);
        }
 
+      if (generate_globals)
+       {
+         add_global (FUNCTION, name, maxargs);
+         continue;
+       }
+
       while (c == ' ' || c == '\n' || c == '\r' || c == '\t')
        c = getc (infile);
 
@@ -723,10 +943,10 @@ scan_c_file (filename, mode)
 
          putc (037, outfile);
          putc (defvarflag ? 'V' : 'F', outfile);
-         fprintf (outfile, "%s\n", buf);
+         fprintf (outfile, "%s\n", input_buffer);
 
          if (comment)
-           getc (infile);      /* Skip past `*' */
+           getc (infile);      /* Skip past `*' */
          c = read_c_string_or_comment (infile, 1, comment, &saw_usage);
 
          /* If this is a defun, find the arguments and print them.  If
@@ -766,11 +986,12 @@ scan_c_file (filename, mode)
              *p = '\0';
              /* Output them.  */
              fprintf (outfile, "\n\n");
-             write_c_args (outfile, buf, argbuf, minargs, maxargs);
+             write_c_args (outfile, input_buffer, argbuf, minargs, maxargs);
            }
          else if (defunflag && maxargs == -1 && !saw_usage)
            /* The DOC should provide the usage form.  */
-           fprintf (stderr, "Missing `usage' for function `%s'.\n", buf);
+           fprintf (stderr, "Missing `usage' for function `%s'.\n",
+                    input_buffer);
        }
     }
  eof:
@@ -806,17 +1027,16 @@ scan_c_file (filename, mode)
  arglist, but the doc string must still have a backslash and newline
  immediately after the double quote.
  The only source files that must follow this convention are preloaded
- uncompiled ones like loaddefs.el and bindings.el; aside
- from that, it is always the .elc file that we look at, and they are no
problem because byte-compiler output follows this convention.
+ uncompiled ones like loaddefs.el; aside from that, it is always the .elc
+ file that we should look at, and they are no problem because byte-compiler
+ output follows this convention.
  The NAME and DOCSTRING are output.
  NAME is preceded by `F' for a function or `V' for a variable.
- An entry is output only if DOCSTRING has \ newline just after the opening "
+ An entry is output only if DOCSTRING has \ newline just after the opening ".
  */
 
-void
-skip_white (infile)
-     FILE *infile;
+static void
+skip_white (FILE *infile)
 {
   char c = ' ';
   while (c == ' ' || c == '\t' || c == '\n' || c == '\r')
@@ -824,10 +1044,8 @@ skip_white (infile)
   ungetc (c, infile);
 }
 
-void
-read_lisp_symbol (infile, buffer)
-     FILE *infile;
-     char *buffer;
+static void
+read_lisp_symbol (FILE *infile, char *buffer)
 {
   char c;
   char *fillp = buffer;
@@ -854,19 +1072,83 @@ read_lisp_symbol (infile, buffer)
   skip_white (infile);
 }
 
-int
-scan_lisp_file (filename, mode)
-     char *filename, *mode;
+static int
+search_lisp_doc_at_eol (FILE *infile)
+{
+  char c = 0, c1 = 0, c2 = 0;
+
+  /* Skip until the end of line; remember two previous chars.  */
+  while (c != '\n' && c != '\r' && c != EOF)
+    {
+      c2 = c1;
+      c1 = c;
+      c = getc (infile);
+    }
+
+  /* If two previous characters were " and \,
+     this is a doc string.  Otherwise, there is none.  */
+  if (c2 != '"' || c1 != '\\')
+    {
+#ifdef DEBUG
+      fprintf (stderr, "## non-docstring found\n");
+#endif
+      if (c != EOF)
+       ungetc (c, infile);
+      return 0;
+    }
+  return 1;
+}
+
+#define DEF_ELISP_FILE(fn)  { #fn, sizeof(#fn) - 1 }
+
+static int
+scan_lisp_file (const char *filename, const char *mode)
 {
   FILE *infile;
   register int c;
   char *saved_string = 0;
+  /* These are the only files that are loaded uncompiled, and must
+     follow the conventions of the doc strings expected by this
+     function.  These conventions are automatically followed by the
+     byte compiler when it produces the .elc files.  */
+  static struct {
+    const char *fn;
+    size_t fl;
+  } const uncompiled[] = {
+    DEF_ELISP_FILE (loaddefs.el),
+    DEF_ELISP_FILE (loadup.el),
+    DEF_ELISP_FILE (charprop.el),
+    DEF_ELISP_FILE (cp51932.el),
+    DEF_ELISP_FILE (eucjp-ms.el)
+  };
+  int i, match;
+  size_t flen = strlen (filename);
+
+  if (generate_globals)
+    fatal ("scanning lisp file when -g specified", 0);
+  if (flen > 3 && !strcmp (filename + flen - 3, ".el"))
+    {
+      for (i = 0, match = 0; i < sizeof (uncompiled) / sizeof (uncompiled[0]);
+          i++)
+       {
+         if (uncompiled[i].fl <= flen
+             && !strcmp (filename + flen - uncompiled[i].fl, uncompiled[i].fn)
+             && (flen == uncompiled[i].fl
+                 || IS_SLASH (filename[flen - uncompiled[i].fl - 1])))
+           {
+             match = 1;
+             break;
+           }
+       }
+      if (!match)
+       fatal ("uncompiled lisp file %s is not supported", filename);
+    }
 
   infile = fopen (filename, mode);
   if (infile == NULL)
     {
       perror (filename);
-      return 0;                                /* No error */
+      return 0;                                /* No error */
     }
 
   c = '\n';
@@ -890,8 +1172,8 @@ scan_lisp_file (filename, mode)
          c = getc (infile);
          if (c == '@')
            {
-             int length = 0;
-             int i;
+             size_t length = 0;
+             size_t i;
 
              /* Read the length.  */
              while ((c = getc (infile),
@@ -901,6 +1183,12 @@ scan_lisp_file (filename, mode)
                  length += c - '0';
                }
 
+             if (length <= 1)
+               fatal ("invalid dynamic doc string length", "");
+
+             if (c != ' ')
+               fatal ("space not found after dynamic doc string length", "");
+
              /* The next character is a space that is counted in the length
                 but not part of the doc string.
                 We already read it, so just ignore it.  */
@@ -916,7 +1204,7 @@ scan_lisp_file (filename, mode)
                 but it is redundant in DOC.  So get rid of it here.  */
              saved_string[length - 1] = 0;
              /* Skip the line break.  */
-             while (c == '\n' && c == '\r')
+             while (c == '\n' || c == '\r')
                c = getc (infile);
              /* Skip the following line.  */
              while (c != '\n' && c != '\r')
@@ -937,7 +1225,7 @@ scan_lisp_file (filename, mode)
          type = 'F';
          read_lisp_symbol (infile, buffer);
 
-         /* Skip the arguments: either "nil" or a list in parens */
+         /* Skip the arguments: either "nil" or a list in parens */
 
          c = getc (infile);
          if (c == 'n') /* nil */
@@ -976,42 +1264,23 @@ scan_lisp_file (filename, mode)
            }
        }
 
+      /* defcustom can only occur in uncompiled Lisp files.  */
       else if (! strcmp (buffer, "defvar")
-              || ! strcmp (buffer, "defconst"))
+              || ! strcmp (buffer, "defconst")
+              || ! strcmp (buffer, "defcustom"))
        {
-         char c1 = 0, c2 = 0;
          type = 'V';
          read_lisp_symbol (infile, buffer);
 
          if (saved_string == 0)
-           {
-
-             /* Skip until the end of line; remember two previous chars.  */
-             while (c != '\n' && c != '\r' && c >= 0)
-               {
-                 c2 = c1;
-                 c1 = c;
-                 c = getc (infile);
-               }
-
-             /* If two previous characters were " and \,
-                this is a doc string.  Otherwise, there is none.  */
-             if (c2 != '"' || c1 != '\\')
-               {
-#ifdef DEBUG
-                 fprintf (stderr, "## non-docstring in %s (%s)\n",
-                          buffer, filename);
-#endif
-                 continue;
-               }
-           }
+           if (!search_lisp_doc_at_eol (infile))
+             continue;
        }
 
       else if (! strcmp (buffer, "custom-declare-variable")
               || ! strcmp (buffer, "defvaralias")
               )
        {
-         char c1 = 0, c2 = 0;
          type = 'V';
 
          c = getc (infile);
@@ -1046,31 +1315,12 @@ scan_lisp_file (filename, mode)
            }
 
          if (saved_string == 0)
-           {
-             /* Skip to end of line; remember the two previous chars.  */
-             while (c != '\n' && c != '\r' && c >= 0)
-               {
-                 c2 = c1;
-                 c1 = c;
-                 c = getc (infile);
-               }
-
-             /* If two previous characters were " and \,
-                this is a doc string.  Otherwise, there is none.  */
-             if (c2 != '"' || c1 != '\\')
-               {
-#ifdef DEBUG
-                 fprintf (stderr, "## non-docstring in %s (%s)\n",
-                          buffer, filename);
-#endif
-                 continue;
-               }
-           }
+           if (!search_lisp_doc_at_eol (infile))
+             continue;
        }
 
       else if (! strcmp (buffer, "fset") || ! strcmp (buffer, "defalias"))
        {
-         char c1 = 0, c2 = 0;
          type = 'F';
 
          c = getc (infile);
@@ -1103,26 +1353,8 @@ scan_lisp_file (filename, mode)
            }
 
          if (saved_string == 0)
-           {
-             /* Skip to end of line; remember the two previous chars.  */
-             while (c != '\n' && c != '\r' && c >= 0)
-               {
-                 c2 = c1;
-                 c1 = c;
-                 c = getc (infile);
-               }
-
-             /* If two previous characters were " and \,
-                this is a doc string.  Otherwise, there is none.  */
-             if (c2 != '"' || c1 != '\\')
-               {
-#ifdef DEBUG
-                 fprintf (stderr, "## non-docstring in %s (%s)\n",
-                          buffer, filename);
-#endif
-                 continue;
-               }
-           }
+           if (!search_lisp_doc_at_eol (infile))
+             continue;
        }
 
       else if (! strcmp (buffer, "autoload"))
@@ -1164,29 +1396,16 @@ scan_lisp_file (filename, mode)
              continue;
            }
          read_c_string_or_comment (infile, 0, 0, 0);
-         skip_white (infile);
 
          if (saved_string == 0)
-           {
-             /* If the next three characters aren't `dquote bslash newline'
-                then we're not reading a docstring.  */
-             if ((c = getc (infile)) != '"'
-                 || (c = getc (infile)) != '\\'
-                 || ((c = getc (infile)) != '\n' && c != '\r'))
-               {
-#ifdef DEBUG
-                 fprintf (stderr, "## non-docstring in %s (%s)\n",
-                          buffer, filename);
-#endif
-                 continue;
-               }
-           }
+           if (!search_lisp_doc_at_eol (infile))
+             continue;
        }
 
 #ifdef DEBUG
       else if (! strcmp (buffer, "if")
               || ! strcmp (buffer, "byte-code"))
-       ;
+       continue;
 #endif
 
       else
@@ -1198,12 +1417,10 @@ scan_lisp_file (filename, mode)
          continue;
        }
 
-      /* At this point, we should either use the previous
-        dynamic doc string in saved_string
-        or gobble a doc string from the input file.
-
-        In the latter case, the opening quote (and leading
-        backslash-newline) have already been read.  */
+      /* At this point, we should either use the previous dynamic doc string in
+        saved_string or gobble a doc string from the input file.
+        In the latter case, the opening quote (and leading backslash-newline)
+        have already been read.  */
 
       putc (037, outfile);
       putc (type, outfile);
@@ -1222,7 +1439,5 @@ scan_lisp_file (filename, mode)
   return 0;
 }
 
-/* arch-tag: f7203aaf-991a-4238-acb5-601db56f2894
-   (do not change this comment) */
 
 /* make-docfile.c ends here */