diff lib-src/make-docfile.c @ 428:3ecd8885ac67 r21-2-22

Import from CVS: tag r21-2-22
author cvs
date Mon, 13 Aug 2007 11:28:15 +0200
parents
children 84b14dcb0985
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib-src/make-docfile.c	Mon Aug 13 11:28:15 2007 +0200
@@ -0,0 +1,1097 @@
+/* Generate doc-string file for XEmacs from source files.
+   Copyright (C) 1985, 1986, 1992, 1993, 1994 Free Software Foundation, Inc.
+   Copyright (C) 1995 Board of Trustees, University of Illinois.
+   Copyright (C) 1998, 1999 J. Kean Johnston.
+
+This file is part of XEmacs.
+
+XEmacs 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, or (at your option) any
+later version.
+
+XEmacs 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 XEmacs; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.  */
+
+/* Synched up with: FSF 19.30. */
+
+/* The arguments given to this program are all the C and Lisp source files
+ of XEmacs.  .elc and .el and .c files are allowed.
+ A .o file can also be specified; the .c file it was made from is used.
+ This helps the makefile pass the correct list of files.
+
+ The results, which go to standard output or to a file
+ specified with -a or -o (-a to append, -o to start from nothing),
+ are entries containing function or variable names and their documentation.
+ Each entry starts with a ^_ character.
+ Then comes F for a function or V for a variable.
+ Then comes the function or variable name, terminated with a newline.
+ Then comes the documentation for that function or variable.
+
+ Added 19.15/20.1:  `-i site-packages' allow installer to dump extra packages
+ without modifying Makefiles, etc.
+ */
+
+#define NO_SHORTNAMES   /* Tell config not to load remap.h */
+#include <../src/config.h>
+
+#include <stdio.h>
+#include <errno.h>
+#if __STDC__ || defined(STDC_HEADERS)
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#endif
+
+#if defined(MSDOS) || defined(__CYGWIN32__)
+#include <fcntl.h>
+#endif /* MSDOS */
+#ifdef WINDOWSNT
+#include <direct.h>
+#include <fcntl.h>
+#include <io.h>
+#include <stdlib.h>
+#endif /* WINDOWSNT */
+
+#include <sys/param.h>
+
+#if defined(DOS_NT) || defined(__CYGWIN32__)
+#define READ_TEXT "rt"
+#define READ_BINARY "rb"
+#define WRITE_BINARY "wb"
+#define APPEND_BINARY "ab"
+#else  /* not DOS_NT */
+#define READ_TEXT "r"
+#define READ_BINARY "r"
+#define WRITE_BINARY "w"
+#define APPEND_BINARY "a"
+#endif /* not DOS_NT */
+
+#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
+
+/* Stdio stream for output to the DOC file.  */
+static FILE *outfile;
+
+enum
+{
+  el_file,
+  elc_file,
+  c_file
+} Current_file_type;
+
+static int scan_file (CONST char *filename);
+static int read_c_string (FILE *, int, int);
+static void write_c_args (FILE *out, CONST char *func, char *buf, int minargs,
+			  int maxargs);
+static int scan_c_file (CONST char *filename, CONST char *mode);
+static void skip_white (FILE *);
+static void read_lisp_symbol (FILE *, char *);
+static int scan_lisp_file (CONST char *filename, CONST char *mode);
+
+#define C_IDENTIFIER_CHAR_P(c) \
+ (('A' <= c && c <= 'Z') || \
+  ('a' <= c && c <= 'z') || \
+  ('0' <= c && c <= '9') || \
+  (c == '_'))
+
+/* Name this program was invoked with.  */
+char *progname;
+
+/* Set to 1 if this was invoked by ellcc */
+int ellcc = 0;
+
+/* Print error message.  `s1' is printf control string, `s2' is arg for it. */
+
+static void
+error (CONST char *s1, CONST char *s2)
+{
+  fprintf (stderr, "%s: ", progname);
+  fprintf (stderr, s1, s2);
+  fprintf (stderr, "\n");
+}
+
+/* Print error message and exit.  */
+
+static void
+fatal (CONST char *s1, CONST char *s2)
+{
+  error (s1, s2);
+  exit (1);
+}
+
+/* Like malloc but get fatal error if memory is exhausted.  */
+
+static long *
+xmalloc (unsigned int size)
+{
+  long *result = (long *) malloc (size);
+  if (result == NULL)
+    fatal ("virtual memory exhausted", 0);
+  return result;
+}
+
+static char *
+next_extra_elc(char *extra_elcs)
+{
+  static FILE *fp = NULL;
+  static char line_buf[BUFSIZ];
+  char *p = line_buf+1;
+
+  if (!fp) {
+    if (!extra_elcs) {
+      return NULL;
+    } else if (!(fp = fopen(extra_elcs, READ_BINARY))) {
+      /* It is not an error if this file doesn't exist. */
+      /*fatal("error opening site package file list", 0);*/
+      return NULL;
+    }
+    fgets(line_buf, BUFSIZ, fp);
+  }
+
+again:
+  if (!fgets(line_buf, BUFSIZ, fp)) {
+    fclose(fp);
+    fp = NULL;
+    return NULL;
+  }
+  line_buf[0] = '\0';
+  if (strlen(p) <= 2 || strlen(p) >= (BUFSIZ - 5)) {
+    /* reject too short or too long lines */
+    goto again;
+  }
+  p[strlen(p) - 2] = '\0';
+  strcat(p, ".elc");
+
+  return p;
+}
+
+
+int
+main (int argc, char **argv)
+{
+  int i;
+  int err_count = 0;
+  int first_infile;
+  char *extra_elcs = NULL;
+
+  progname = argv[0];
+
+  outfile = stdout;
+
+  /* Don't put CRs in the DOC file.  */
+#ifdef MSDOS
+  _fmode = O_BINARY;
+#if 0  /* Suspicion is that this causes hanging.
+	  So instead we require people to use -o on MSDOS.  */
+  (stdout)->_flag &= ~_IOTEXT;
+  _setmode (fileno (stdout), O_BINARY);
+#endif
+  outfile = 0;
+#endif /* MSDOS */
+#ifdef WINDOWSNT
+  _fmode = O_BINARY;
+  _setmode (fileno (stdout), O_BINARY);
+#endif /* WINDOWSNT */
+
+  /* If first two args are -o FILE, output to FILE.  */
+  i = 1;
+  if (argc > i + 1 && !strcmp (argv[i], "-o"))
+    {
+      outfile = fopen (argv[i + 1], WRITE_BINARY);
+      i += 2;
+    }
+  if (argc > i + 1 && !strcmp (argv[i], "-a"))
+    {
+      outfile = fopen (argv[i + 1], APPEND_BINARY);
+      i += 2;
+    }
+  if (argc > i + 1 && !strcmp (argv[i], "-E"))
+    {
+      outfile = fopen (argv[i + 1], APPEND_BINARY);
+      i += 2;
+      ellcc = 1;
+    }
+  if (argc > i + 1 && !strcmp (argv[i], "-d"))
+    {
+      chdir (argv[i + 1]);
+      i += 2;
+    }
+
+  if (argc > (i + 1) && !strcmp(argv[i], "-i")) {
+    extra_elcs = argv[i + 1];
+    i += 2;
+  }
+
+  if (outfile == 0)
+    fatal ("No output file specified", "");
+
+  if (ellcc)
+    fprintf (outfile, "{\n");
+
+  first_infile = i;
+  for (; i < argc; i++)
+    {
+      int j;
+      /* Don't process one file twice.  */
+      for (j = first_infile; j < i; j++)
+	if (! strcmp (argv[i], argv[j]))
+	  break;
+      if (j == i)
+	/* err_count seems to be {mis,un}used */
+	err_count += scan_file (argv[i]);
+    }
+
+  if (extra_elcs) {
+    char *p;
+
+    while ((p = next_extra_elc(extra_elcs)) != NULL) {
+      err_count += scan_file(p);
+    }
+  }
+
+  putc ('\n', outfile);
+  if (ellcc)
+    fprintf (outfile, "}\n\n");
+#ifndef VMS
+  exit (err_count > 0);
+#endif /* VMS */
+  return err_count > 0;
+}
+
+/* Read file FILENAME and output its doc strings to outfile.  */
+/* Return 1 if file is not found, 0 if it is found.  */
+
+static int
+scan_file (CONST char *filename)
+{
+  int len = strlen (filename);
+  if (ellcc == 0 && len > 4 && !strcmp (filename + len - 4, ".elc"))
+    {
+      Current_file_type = elc_file;
+      return scan_lisp_file (filename, READ_BINARY);
+    }
+  else if (ellcc == 0 && len > 3 && !strcmp (filename + len - 3, ".el"))
+    {
+      Current_file_type = el_file;
+      return scan_lisp_file (filename, READ_TEXT);
+    }
+  else
+    {
+      Current_file_type = c_file;
+      return scan_c_file (filename, READ_TEXT);
+    }
+}
+
+char buf[128];
+
+/* Skip a C string from INFILE,
+ and return the character that follows the closing ".
+ If printflag is positive, output string contents to outfile.
+ If it is negative, store contents in buf.
+ Convert escape sequences \n and \t to newline and tab;
+ discard \ followed by newline.  */
+
+static int
+read_c_string (FILE *infile, int printflag, int c_docstring)
+{
+  register int c;
+  char *p = buf;
+  int start = -1;
+
+  c = getc (infile);
+  while (c != EOF)
+    {
+      while ((c_docstring || c != '"') && c != EOF)
+	{
+	  if (start)
+	    {
+	      if (c == '*')
+		{
+		  int cc = getc (infile);
+		  if (cc == '/')
+		    break;
+		  else
+		    ungetc (cc, infile);
+		}
+
+	      if (start != -1)
+		{
+		  if (printflag > 0)
+            {
+              if (ellcc)
+                fprintf (outfile, "\\n\\");
+              putc ('\n', outfile);
+            }
+		  else if (printflag < 0)
+		    *p++ = '\n';
+		}
+	    }
+
+	  if (c == '\\')
+	    {
+	      c = getc (infile);
+	      if (c == '\n')
+		{
+		  c = getc (infile);
+		  start = 1;
+		  continue;
+		}
+	      if (!c_docstring && c == 'n')
+		c = '\n';
+	      if (c == 't')
+		c = '\t';
+	    }
+	  if (c == '\n')
+	    start = 1;
+	  else
+	    {
+	      start = 0;
+	      if (printflag > 0) {
+                if (ellcc && c == '"')
+                  putc ('\\', outfile);
+		putc (c, outfile);
+              }
+	      else if (printflag < 0)
+		*p++ = c;
+	    }
+	  c = getc (infile);
+	}
+      /* look for continuation of string */
+      if (Current_file_type == c_file)
+	{
+	  while (isspace (c = getc (infile)))
+	    ;
+	  if (c != '"')
+	    break;
+	}
+      else
+	{
+	  c = getc (infile);
+	  if (c != '"')
+	    break;
+	  /* If we had a "", concatenate the two strings.  */
+	}
+      c = getc (infile);
+    }
+
+  if (printflag < 0)
+    *p = 0;
+
+  return c;
+}
+
+/* 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.  */
+
+static void
+write_c_args (FILE *out, CONST char *func, char *buff, int minargs,
+	      int maxargs)
+{
+  register char *p;
+  int in_ident = 0;
+  int just_spaced = 0;
+#if 0
+  int need_space = 1;
+
+  fprintf (out, "(%s", func);
+#else
+  /* XEmacs - "arguments:" is for parsing the docstring.  FSF's help system
+     doesn't parse the docstring for arguments like we do, so we're also
+     going to omit the function name to preserve compatibility with elisp
+     that parses the docstring.  Finally, not prefixing the arglist with
+     anything is asking for trouble because it's not uncommon to have an
+     unescaped parenthesis at the beginning of a line. --Stig */
+  fprintf (out, "arguments: (");
+#endif
+
+  if (*buff == '(')
+    ++buff;
+
+  for (p = buff; *p; p++)
+    {
+      char c = *p;
+      int ident_start = 0;
+
+      /* Add support for ANSI prototypes. Hop over
+	 "Lisp_Object" string (the only C type allowed in DEFUNs) */
+      static char lo[] = "Lisp_Object";
+      if ((C_IDENTIFIER_CHAR_P (c) != in_ident) && !in_ident &&
+	  (strncmp (p, lo, sizeof (lo) - 1) == 0) &&
+	  isspace(*(p + sizeof (lo) - 1)))
+	{
+	  p += (sizeof (lo) - 1);
+	  while (isspace (*p))
+	    p++;
+	  c = *p;
+	}
+
+      /* Notice when we start printing a new identifier.  */
+      if (C_IDENTIFIER_CHAR_P (c) != in_ident)
+	{
+	  if (!in_ident)
+	    {
+	      in_ident = 1;
+	      ident_start = 1;
+#if 0
+	      /* XEmacs - This goes along with the change above. */
+	      if (need_space)
+		putc (' ', out);
+#endif
+	      if (minargs == 0 && maxargs > 0)
+		fprintf (out, "&optional ");
+	      just_spaced = 1;
+
+	      minargs--;
+	      maxargs--;
+	    }
+	  else
+	    in_ident = 0;
+	}
+
+      /* Print the C argument list as it would appear in lisp:
+	 print underscores as hyphens, and print commas as spaces.
+	 Collapse adjacent spaces into one. */
+      if (c == '_') c = '-';
+      if (c == ',') c = ' ';
+
+      /* If the C argument name ends with `_', change it to ' ',
+	 to allow use of C reserved words or global symbols as Lisp args. */
+      if (c == '-' && ! C_IDENTIFIER_CHAR_P (p[1]))
+	{
+	  in_ident = 0;
+	  just_spaced = 0;
+	}
+      else if (c != ' ' || ! just_spaced)
+	{
+	  if (c >= 'a' && c <= 'z')
+	    /* Upcase the letter.  */
+	    c += 'A' - 'a';
+	  putc (c, out);
+	}
+
+      just_spaced = (c == ' ');
+#if 0
+      need_space = 0;
+#endif
+    }
+  if (!ellcc)
+    putc ('\n', out); /* XEmacs addition */
+}
+
+/* Read through a c file.  If a .o file is named,
+   the corresponding .c 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.  */
+
+static int
+scan_c_file (CONST char *filename, CONST char *mode)
+{
+  FILE *infile;
+  register int c;
+  register int commas;
+  register int defunflag;
+  register int defvarperbufferflag = 0;
+  register int defvarflag;
+  int minargs, maxargs;
+  int l = strlen (filename);
+  char f[MAXPATHLEN];
+
+  if (l > sizeof (f))
+  {
+#ifdef ENAMETOOLONG
+    errno = ENAMETOOLONG;
+#else
+    errno = EINVAL;
+#endif
+    return (0);
+  }
+
+  strcpy (f, filename);
+  if (f[l - 1] == 'o')
+    f[l - 1] = 'c';
+  infile = fopen (f, mode);
+
+  /* No error if non-ex input file */
+  if (infile == NULL)
+    {
+      perror (f);
+      return 0;
+    }
+
+  c = '\n';
+  while (!feof (infile))
+    {
+      if (c != '\n')
+	{
+	  c = getc (infile);
+	  continue;
+	}
+      c = getc (infile);
+      if (c == ' ')
+	{
+	  while (c == ' ')
+	    c = getc (infile);
+	  if (c != 'D')
+	    continue;
+	  c = getc (infile);
+	  if (c != 'E')
+	    continue;
+	  c = getc (infile);
+	  if (c != 'F')
+	    continue;
+	  c = getc (infile);
+	  if (c != 'V')
+	    continue;
+	  c = getc (infile);
+	  if (c != 'A')
+	    continue;
+	  c = getc (infile);
+	  if (c != 'R')
+	    continue;
+	  c = getc (infile);
+	  if (c != '_')
+	    continue;
+
+	  defvarflag = 1;
+	  defunflag = 0;
+
+	  c = getc (infile);
+	  /* Note that this business doesn't apply under XEmacs.
+	     DEFVAR_BUFFER_LOCAL in XEmacs behaves normally. */
+	  defvarperbufferflag = (c == 'P');
+
+	  c = getc (infile);
+	}
+      else if (c == 'D')
+	{
+	  c = getc (infile);
+	  if (c != 'E')
+	    continue;
+	  c = getc (infile);
+	  if (c != 'F')
+	    continue;
+	  c = getc (infile);
+	  defunflag = (c == 'U');
+	  defvarflag = 0;
+	  c = getc (infile);
+	}
+      else continue;
+
+      while (c != '(')
+	{
+	  if (c < 0)
+	    goto eof;
+	  c = getc (infile);
+	}
+
+      c = getc (infile);
+      if (c != '"')
+	continue;
+      c = read_c_string (infile, -1, 0);
+
+      if (defunflag)
+	commas = 4;
+      else if (defvarperbufferflag)
+	commas = 2;
+      else if (defvarflag)
+	commas = 1;
+      else  /* For DEFSIMPLE and DEFPRED */
+	commas = 2;
+
+      while (commas)
+	{
+	  if (c == ',')
+	    {
+	      commas--;
+	      if (defunflag && (commas == 1 || commas == 2))
+		{
+		  do
+		    c = getc (infile);
+		  while (c == ' ' || c == '\n' || 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 (c == 'M' || c == 'U') /* MANY || UNEVALLED */
+		      maxargs = -1;
+		    else
+		      fscanf (infile, "%d", &maxargs);
+		}
+	    }
+	  if (c < 0)
+	    goto eof;
+	  c = getc (infile);
+	}
+      while (c == ' ' || c == '\n' || c == '\t')
+	c = getc (infile);
+      if (c == '"')
+	c = read_c_string (infile, 0, 0);
+      if (defunflag | defvarflag)
+	{
+	  while (c != '/')
+	    c = getc (infile);
+	  c = getc (infile);
+	  while (c == '*')
+	    c = getc (infile);
+	}
+      else
+	{
+	  while (c != ',')
+	    c = getc (infile);
+	  c = getc (infile);
+	}
+      while (c == ' ' || c == '\n' || c == '\t')
+	c = getc (infile);
+      if (defunflag | defvarflag)
+	ungetc (c, infile);
+
+      if (defunflag || defvarflag || c == '"')
+	{
+      if (ellcc)
+        fprintf (outfile, "  CDOC%s(\"%s\", \"\\\n",
+                 defvarflag ? "SYM" : "SUBR", buf);
+      else
+        {
+          putc (037, outfile);
+          putc (defvarflag ? 'V' : 'F', outfile);
+          fprintf (outfile, "%s\n", buf);
+        }
+	  c = read_c_string (infile, 1, (defunflag || defvarflag));
+
+	  /* If this is a defun, find the arguments and print them.  If
+	     this function takes MANY or UNEVALLED args, then the C source
+	     won't give the names of the arguments, so we shouldn't bother
+	     trying to find them.  */
+	  if (defunflag && maxargs != -1)
+	    {
+	      char argbuf[1024], *p = argbuf;
+#if 0 /* For old DEFUN's only */
+	      while (c != ')')
+		{
+		  if (c < 0)
+		    goto eof;
+		  c = getc (infile);
+		}
+#endif
+	      /* Skip into arguments.  */
+	      while (c != '(')
+		{
+		  if (c < 0)
+		    goto eof;
+		  c = getc (infile);
+		}
+	      /* Copy arguments into ARGBUF.  */
+	      *p++ = c;
+	      do
+		*p++ = c = getc (infile);
+	      while (c != ')');
+	      *p = '\0';
+	      /* Output them.  */
+          if (ellcc)
+            fprintf (outfile, "\\n\\\n\\n\\\n");
+          else
+            fprintf (outfile, "\n\n");
+	      write_c_args (outfile, buf, argbuf, minargs, maxargs);
+	    }
+      if (ellcc)
+        fprintf (outfile, "\\n\");\n\n");
+	}
+    }
+ eof:
+  fclose (infile);
+  return 0;
+}
+
+/* Read a file of Lisp code, compiled or interpreted.
+ Looks for
+  (defun NAME ARGS DOCSTRING ...)
+  (defmacro NAME ARGS DOCSTRING ...)
+  (autoload (quote NAME) FILE DOCSTRING ...)
+  (defvar NAME VALUE DOCSTRING)
+  (defconst NAME VALUE DOCSTRING)
+  (fset (quote NAME) (make-byte-code ... DOCSTRING ...))
+  (fset (quote NAME) #[... DOCSTRING ...])
+  (defalias (quote NAME) #[... DOCSTRING ...])
+ starting in column zero.
+ (quote NAME) may appear as 'NAME as well.
+
+ We also look for #@LENGTH CONTENTS^_ at the beginning of the line.
+ When we find that, we save it for the following defining-form,
+ and we use that instead of reading a doc string within that defining-form.
+
+ For defun, defmacro, and autoload, we know how to skip over the arglist.
+ For defvar, defconst, and fset we skip to the docstring with a kludgy
+ formatting convention: all docstrings must appear on the same line as the
+ initial open-paren (the one in column zero) and must contain a backslash
+ and a double-quote immediately after the initial double-quote.  No newlines
+ must appear between the beginning of the form and the first double-quote.
+ The only source file that must follow this convention is loaddefs.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.
+ 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 "
+ */
+
+static void
+skip_white (FILE *infile)
+{
+  char c = ' ';
+  while (c == ' ' || c == '\t' || c == '\n')
+    c = getc (infile);
+  ungetc (c, infile);
+}
+
+static void
+read_lisp_symbol (FILE *infile, char *buffer)
+{
+  char c;
+  char *fillp = buffer;
+
+  skip_white (infile);
+  while (1)
+    {
+      c = getc (infile);
+      if (c == '\\')
+	/* FSF has *(++fillp), which is wrong. */
+	*fillp++ = getc (infile);
+      else if (c == ' ' || c == '\t' || c == '\n' || c == '(' || c == ')')
+	{
+	  ungetc (c, infile);
+	  *fillp = 0;
+	  break;
+	}
+      else
+	*fillp++ = c;
+    }
+
+  if (! buffer[0])
+    fprintf (stderr, "## expected a symbol, got '%c'\n", c);
+
+  skip_white (infile);
+}
+
+static int
+scan_lisp_file (CONST char *filename, CONST char *mode)
+{
+  FILE *infile;
+  register int c;
+  char *saved_string = 0;
+
+  infile = fopen (filename, mode);
+  if (infile == NULL)
+    {
+      perror (filename);
+      return 0;				/* No error */
+    }
+
+  c = '\n';
+  while (!feof (infile))
+    {
+      char buffer[BUFSIZ];
+      char type;
+
+      if (c != '\n')
+	{
+	  c = getc (infile);
+	  continue;
+	}
+      c = getc (infile);
+      /* Detect a dynamic doc string and save it for the next expression.  */
+      if (c == '#')
+	{
+	  c = getc (infile);
+	  if (c == '@')
+	    {
+	      int length = 0;
+	      int i;
+
+	      /* Read the length.  */
+	      while ((c = getc (infile),
+		      c >= '0' && c <= '9'))
+		{
+		  length *= 10;
+		  length += c - '0';
+		}
+
+	      /* 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.  */
+	      length--;
+
+	      /* Read in the contents.  */
+	      if (saved_string != 0)
+		free (saved_string);
+	      saved_string = (char *) xmalloc (length);
+	      for (i = 0; i < length; i++)
+		saved_string[i] = getc (infile);
+	      /* The last character is a ^_.
+		 That is needed in the .elc file
+		 but it is redundant in DOC.  So get rid of it here.  */
+	      saved_string[length - 1] = 0;
+	      /* Skip the newline.  */
+	      c = getc (infile);
+	      while (c != '\n')
+		c = getc (infile);
+	    }
+	  continue;
+	}
+
+      if (c != '(')
+	continue;
+
+      read_lisp_symbol (infile, buffer);
+
+      if (! strcmp (buffer, "defun") ||
+	  ! strcmp (buffer, "defmacro"))
+	{
+	  type = 'F';
+	  read_lisp_symbol (infile, buffer);
+
+	  /* Skip the arguments: either "nil" or a list in parens */
+
+	  c = getc (infile);
+	  if (c == 'n') /* nil */
+	    {
+	      if ((c = getc (infile)) != 'i' ||
+		  (c = getc (infile)) != 'l')
+		{
+		  fprintf (stderr, "## unparsable arglist in %s (%s)\n",
+			   buffer, filename);
+		  continue;
+		}
+	    }
+	  else if (c != '(')
+	    {
+	      fprintf (stderr, "## unparsable arglist in %s (%s)\n",
+		       buffer, filename);
+	      continue;
+	    }
+	  else
+	    while (c != ')')
+	      c = getc (infile);
+	  skip_white (infile);
+
+	  /* 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')
+	    {
+#ifdef DEBUG
+	      fprintf (stderr, "## non-docstring in %s (%s)\n",
+		       buffer, filename);
+#endif
+	      continue;
+	    }
+	}
+
+      else if (! strcmp (buffer, "defvar") ||
+	       ! strcmp (buffer, "defconst"))
+	{
+	  char c1 = 0, c2 = 0;
+	  type = 'V';
+	  read_lisp_symbol (infile, buffer);
+
+	  if (saved_string == 0)
+	    {
+
+	      /* Skip until the first newline; remember the two previous chars. */
+	      while (c != '\n' && c >= 0)
+		{
+		  /* ### Kludge -- Ignore any ESC x x ISO2022 sequences */
+		  if (c == 27)
+		    {
+		      getc (infile);
+		      getc (infile);
+		      goto nextchar;
+		    }
+
+		  c2 = c1;
+		  c1 = c;
+		nextchar:
+		  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;
+		}
+	    }
+	}
+
+      else if (! strcmp (buffer, "fset") || ! strcmp (buffer, "defalias"))
+	{
+	  char c1 = 0, c2 = 0;
+	  type = 'F';
+
+	  c = getc (infile);
+	  if (c == '\'')
+	    read_lisp_symbol (infile, buffer);
+	  else
+	    {
+	      if (c != '(')
+		{
+		  fprintf (stderr, "## unparsable name in fset in %s\n",
+			   filename);
+		  continue;
+		}
+	      read_lisp_symbol (infile, buffer);
+	      if (strcmp (buffer, "quote"))
+		{
+		  fprintf (stderr, "## unparsable name in fset in %s\n",
+			   filename);
+		  continue;
+		}
+	      read_lisp_symbol (infile, buffer);
+	      c = getc (infile);
+	      if (c != ')')
+		{
+		  fprintf (stderr,
+			   "## unparsable quoted name in fset in %s\n",
+			   filename);
+		  continue;
+		}
+	    }
+
+	  if (saved_string == 0)
+	    {
+	      /* Skip until the first newline; remember the two previous chars. */
+	      while (c != '\n' && 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;
+		}
+	    }
+	}
+
+      else if (! strcmp (buffer, "autoload"))
+	{
+	  type = 'F';
+	  c = getc (infile);
+	  if (c == '\'')
+	    read_lisp_symbol (infile, buffer);
+	  else
+	    {
+	      if (c != '(')
+		{
+		  fprintf (stderr, "## unparsable name in autoload in %s\n",
+			   filename);
+		  continue;
+		}
+	      read_lisp_symbol (infile, buffer);
+	      if (strcmp (buffer, "quote"))
+		{
+		  fprintf (stderr, "## unparsable name in autoload in %s\n",
+			   filename);
+		  continue;
+		}
+	      read_lisp_symbol (infile, buffer);
+	      c = getc (infile);
+	      if (c != ')')
+		{
+		  fprintf (stderr,
+			   "## unparsable quoted name in autoload in %s\n",
+			   filename);
+		  continue;
+		}
+	    }
+	  skip_white (infile);
+	  if ((c = getc (infile)) != '\"')
+	    {
+	      fprintf (stderr, "## autoload of %s unparsable (%s)\n",
+		       buffer, filename);
+	      continue;
+	    }
+	  read_c_string (infile, 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')
+		{
+#ifdef DEBUG
+		  fprintf (stderr, "## non-docstring in %s (%s)\n",
+			   buffer, filename);
+#endif
+		  continue;
+		}
+	    }
+	}
+
+#if 0 /* causes crash */
+      else if (! strcmp (buffer, "if") ||
+	       ! strcmp (buffer, "byte-code"))
+	;
+#endif
+
+      else
+	{
+#ifdef DEBUG
+	  fprintf (stderr, "## unrecognized top-level form, %s (%s)\n",
+		   buffer, filename);
+#endif
+	  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.  */
+      putc ('\n', outfile); /* XEmacs addition */
+      putc (037, outfile);
+      putc (type, outfile);
+      fprintf (outfile, "%s\n", buffer);
+      if (saved_string)
+	{
+	  fputs (saved_string, outfile);
+	  /* Don't use one dynamic doc string twice.  */
+	  free (saved_string);
+	  saved_string = 0;
+	}
+      else
+	read_c_string (infile, 1, 0);
+    }
+  fclose (infile);
+  return 0;
+}