diff src/doc.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 8de8e3f6228a
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/doc.c	Mon Aug 13 11:28:15 2007 +0200
@@ -0,0 +1,1006 @@
+/* Record indices of function doc strings stored in a file.
+   Copyright (C) 1985, 1986, 1992, 1993, 1994, 1995
+   Free Software Foundation, Inc.
+
+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. */
+
+/* This file has been Mule-ized except as noted. */
+
+#include <config.h>
+#include "lisp.h"
+
+#include "buffer.h"
+#include "bytecode.h"
+#include "insdel.h"
+#include "keymap.h"
+#include "sysfile.h"
+
+Lisp_Object Vinternal_doc_file_name;
+
+Lisp_Object QSsubstitute;
+
+/* Read and return doc string from open file descriptor FD
+   at position POSITION.  Does not close the file.  Returns
+   string; or if error, returns a cons holding the error
+   data to pass to Fsignal.  NAME_NONRELOC and NAME_RELOC
+   are only used for the error messages. */
+
+Lisp_Object
+unparesseuxify_doc_string (int fd, EMACS_INT position,
+                           char *name_nonreloc, Lisp_Object name_reloc)
+{
+  char buf[512 * 32 + 1];
+  char *buffer = buf;
+  int buffer_size = sizeof (buf);
+  char *from, *to;
+  REGISTER char *p = buffer;
+  Lisp_Object return_me;
+
+  if (0 > lseek (fd, position, 0))
+    {
+      if (name_nonreloc)
+	name_reloc = build_string (name_nonreloc);
+      return_me = list3 (build_string
+			 ("Position out of range in doc string file"),
+			  name_reloc, make_int (position));
+      goto done;
+    }
+
+  /* Read the doc string into a buffer.
+     Use the fixed buffer BUF if it is big enough; otherwise allocate one.
+     We store the buffer in use in BUFFER and its size in BUFFER_SIZE.  */
+
+  while (1)
+    {
+      int space_left = buffer_size - (p - buffer);
+      int nread;
+
+      /* Switch to a bigger buffer if we need one.  */
+      if (space_left == 0)
+	{
+          char * old_buffer = buffer;
+	  if (buffer == buf) {
+            buffer = (char *) xmalloc (buffer_size *= 2);
+            memcpy (buffer, old_buffer, p - old_buffer);
+          } else {
+            buffer = (char *) xrealloc (buffer, buffer_size *= 2);
+          }
+          p += buffer - old_buffer;
+	  space_left = buffer_size - (p - buffer);
+	}
+
+      /* Don't read too much at one go.  */
+      if (space_left > 1024 * 8)
+	space_left = 1024 * 8;
+      nread = read (fd, p, space_left);
+      if (nread < 0)
+	{
+	  return_me = list1 (build_string
+			     ("Read error on documentation file"));
+	  goto done;
+	}
+      p[nread] = 0;
+      if (!nread)
+	break;
+      {
+        char *p1 = strchr (p, '\037'); /* End of doc string marker */
+        if (p1)
+          {
+            *p1 = 0;
+            p = p1;
+            break;
+          }
+      }
+      p += nread;
+    }
+
+  /* Scan the text and remove quoting with ^A (char code 1).
+     ^A^A becomes ^A, ^A0 becomes a null char, and ^A_ becomes a ^_.  */
+  from = to = buffer;
+  while (from < p)
+    {
+      if (*from != 1 /*^A*/)
+	*to++ = *from++;
+      else
+	{
+	  int c = *(++from);
+
+	  from++;
+          switch (c)
+            {
+            case 1:   *to++ =  c;     break;
+            case '0': *to++ = '\0';   break;
+            case '_': *to++ = '\037'; break;
+            default:
+              return_me = list2 (build_string
+	("Invalid data in documentation file -- ^A followed by weird code"),
+                                 make_int (c));
+              goto done;
+            }
+	}
+    }
+
+  /* #### mrb: following STILL completely broken */
+  return_me = make_ext_string ((Bufbyte *) buffer, to - buffer, FORMAT_BINARY);
+
+ done:
+  if (buffer != buf) /* We must have allocated buffer above */
+    xfree (buffer);
+  return return_me;
+}
+
+#define string_join(dest, s1, s2) \
+  memcpy ((void *) dest, (void *) XSTRING_DATA (s1), XSTRING_LENGTH (s1)); \
+  memcpy ((void *) ((Bufbyte *) dest + XSTRING_LENGTH (s1)), \
+          (void *) XSTRING_DATA (s2), XSTRING_LENGTH (s2));  \
+          dest[XSTRING_LENGTH (s1) + XSTRING_LENGTH (s2)] = '\0'
+
+/* Extract a doc string from a file.  FILEPOS says where to get it.
+   (This could actually be byte code instructions/constants instead
+   of a doc string.)
+   If it is an integer, use that position in the standard DOC file.
+   If it is (FILE . INTEGER), use FILE as the file name
+   and INTEGER as the position in that file.
+   But if INTEGER is negative, make it positive.
+   (A negative integer is used for user variables, so we can distinguish
+   them without actually fetching the doc string.)  */
+
+static Lisp_Object
+get_doc_string (Lisp_Object filepos)
+{
+  /* !!#### This function has not been Mule-ized */
+  REGISTER int fd;
+  REGISTER char *name_nonreloc = 0;
+  int minsize;
+  EMACS_INT position;
+  Lisp_Object file, tem;
+  Lisp_Object name_reloc = Qnil;
+
+  if (INTP (filepos))
+    {
+      file = Vinternal_doc_file_name;
+      position = XINT (filepos);
+    }
+  else if (CONSP (filepos) && INTP (XCDR (filepos)))
+    {
+      file = XCAR (filepos);
+      position = XINT (XCDR (filepos));
+      if (position < 0)
+	position = - position;
+    }
+  else
+    return Qnil;
+
+  if (!STRINGP (file))
+    return Qnil;
+
+  /* Put the file name in NAME as a C string.
+     If it is relative, combine it with Vdoc_directory.  */
+
+  tem = Ffile_name_absolute_p (file);
+  if (NILP (tem))
+    {
+      /* XEmacs: Move this check here.  OK if called during loadup to
+	 load byte code instructions. */
+      if (!STRINGP (Vdoc_directory))
+	return Qnil;
+
+      minsize = XSTRING_LENGTH (Vdoc_directory);
+      /* sizeof ("../lib-src/") == 12 */
+      if (minsize < 12)
+	minsize = 12;
+      name_nonreloc = (char *) alloca (minsize + XSTRING_LENGTH (file) + 8);
+      string_join (name_nonreloc, Vdoc_directory, file);
+    }
+  else
+    name_reloc = file;
+
+  fd = open (name_nonreloc ? name_nonreloc :
+	     (char *) XSTRING_DATA (name_reloc), O_RDONLY | OPEN_BINARY, 0);
+  if (fd < 0)
+    {
+#ifndef CANNOT_DUMP
+      if (purify_flag)
+	{
+	    /* sizeof ("../lib-src/") == 12 */
+	  name_nonreloc = (char *) alloca (12 + XSTRING_LENGTH (file) + 8);
+	  /* Preparing to dump; DOC file is probably not installed.
+	     So check in ../lib-src. */
+	  strcpy (name_nonreloc, "../lib-src/");
+	  strcat (name_nonreloc, (char *) XSTRING_DATA (file));
+
+	  fd = open (name_nonreloc, O_RDONLY | OPEN_BINARY, 0);
+	}
+#endif /* CANNOT_DUMP */
+
+      if (fd < 0)
+	error ("Cannot open doc string file \"%s\"",
+	       name_nonreloc ? name_nonreloc :
+	       (char *) XSTRING_DATA (name_reloc));
+    }
+
+  tem = unparesseuxify_doc_string (fd, position, name_nonreloc, name_reloc);
+  close (fd);
+
+  if (!STRINGP (tem))
+    signal_error (Qerror, tem);
+
+  return tem;
+}
+
+/* Get a string from position FILEPOS and pass it through the Lisp reader.
+   We use this for fetching the bytecode string and constants vector
+   of a compiled function from the .elc file.  */
+
+Lisp_Object
+read_doc_string (Lisp_Object filepos)
+{
+  Lisp_Object string = get_doc_string (filepos);
+
+  if (!STRINGP (string))
+    signal_simple_error ("loading bytecode failed to return string", string);
+  return Fread (string);
+}
+
+DEFUN ("documentation", Fdocumentation, 1, 2, 0, /*
+Return the documentation string of FUNCTION.
+Unless a non-nil second argument is given, the
+string is passed through `substitute-command-keys'.
+*/
+       (function, raw))
+{
+  /* This function can GC */
+  Lisp_Object fun;
+  Lisp_Object doc;
+
+  fun = Findirect_function (function);
+
+  if (SUBRP (fun))
+    {
+      if (XSUBR (fun)->doc == 0)
+	return Qnil;
+      if ((EMACS_INT) XSUBR (fun)->doc >= 0)
+	doc = build_string (XSUBR (fun)->doc);
+      else
+        doc = get_doc_string (make_int (- (EMACS_INT) XSUBR (fun)->doc));
+    }
+  else if (COMPILED_FUNCTIONP (fun))
+    {
+      Lisp_Object tem;
+      struct Lisp_Compiled_Function *f = XCOMPILED_FUNCTION (fun);
+      if (! (f->flags.documentationp))
+        return Qnil;
+      tem = compiled_function_documentation (f);
+      if (STRINGP (tem))
+	doc = tem;
+      else if (NATNUMP (tem) || CONSP (tem))
+	doc = get_doc_string (tem);
+      else
+        return Qnil;
+    }
+  else if (KEYMAPP (fun))
+    return build_translated_string ("Prefix command (definition is a keymap of subcommands).");
+  else if (STRINGP (fun) || VECTORP (fun))
+    return build_translated_string ("Keyboard macro.");
+  else if (CONSP (fun))
+    {
+      Lisp_Object funcar = Fcar (fun);
+
+      if (!SYMBOLP (funcar))
+	return Fsignal (Qinvalid_function, list1 (fun));
+      else if (EQ (funcar, Qlambda)
+             || EQ (funcar, Qautoload))
+	{
+	  Lisp_Object tem, tem1;
+	  tem1 = Fcdr (Fcdr (fun));
+	  tem = Fcar (tem1);
+	  if (STRINGP (tem))
+	    doc = tem;
+	  /* Handle a doc reference--but these never come last
+	     in the function body, so reject them if they are last.  */
+	  else if ((NATNUMP (tem) || CONSP (tem))
+		   && ! NILP (XCDR (tem1)))
+	    doc = get_doc_string (tem);
+	  else
+	    return Qnil;
+	}
+      else if (EQ (funcar, Qmacro))
+	return Fdocumentation (Fcdr (fun), raw);
+      else
+	goto oops;
+    }
+  else
+    {
+    oops:
+      return Fsignal (Qinvalid_function, list1 (fun));
+    }
+
+  if (NILP (raw))
+    {
+      struct gcpro gcpro1;
+#ifdef I18N3
+      Lisp_Object domain = Qnil;
+      if (COMPILED_FUNCTIONP (fun))
+	domain = compiled_function_domain (XCOMPILED_FUNCTION (fun));
+      if (NILP (domain))
+	doc = Fgettext (doc);
+      else
+	doc = Fdgettext (domain, doc);
+#endif
+
+      GCPRO1 (doc);
+      doc = Fsubstitute_command_keys (doc);
+      UNGCPRO;
+    }
+  return doc;
+}
+
+DEFUN ("documentation-property", Fdocumentation_property, 2, 3, 0, /*
+Return the documentation string that is SYMBOL's PROP property.
+This is like `get', but it can refer to strings stored in the
+`doc-directory/DOC' file; and if the value is a string, it is passed
+through `substitute-command-keys'.  A non-nil third argument avoids this
+translation.
+*/
+       (sym, prop, raw))
+{
+  /* This function can GC */
+  REGISTER Lisp_Object doc = Qnil;
+#ifdef I18N3
+  REGISTER Lisp_Object domain;
+#endif
+  struct gcpro gcpro1;
+
+  GCPRO1 (doc);
+
+  doc = Fget (sym, prop, Qnil);
+  if (INTP (doc))
+    doc = get_doc_string (XINT (doc) > 0 ? doc : make_int (- XINT (doc)));
+  else if (CONSP (doc))
+    doc = get_doc_string (doc);
+#ifdef I18N3
+  if (!NILP (doc))
+    {
+      domain = Fget (sym, Qvariable_domain, Qnil);
+      if (NILP (domain))
+	doc = Fgettext (doc);
+      else
+	doc = Fdgettext (domain, doc);
+    }
+#endif
+  if (NILP (raw) && STRINGP (doc))
+    doc = Fsubstitute_command_keys (doc);
+  UNGCPRO;
+  return doc;
+}
+
+static void
+weird_doc (Lisp_Object sym, CONST char *weirdness, CONST char *type, int pos)
+{
+  if (!strcmp (weirdness, GETTEXT ("duplicate"))) return;
+  message ("Note: Strange doc (%s) for %s %s @ %d",
+           weirdness, type, string_data (XSYMBOL (sym)->name), pos);
+}
+
+
+DEFUN ("Snarf-documentation", Fsnarf_documentation, 1, 1, 0, /*
+Used during Emacs initialization, before dumping runnable Emacs,
+to find pointers to doc strings stored in `.../lib-src/DOC' and
+record them in function definitions.
+One arg, FILENAME, a string which does not include a directory.
+The file is written to `../lib-src', and later found in `exec-directory'
+when doc strings are referred to in the dumped Emacs.
+*/
+       (filename))
+{
+  /* !!#### This function has not been Mule-ized */
+  int fd;
+  char buf[1024 + 1];
+  REGISTER int filled;
+  REGISTER int pos;
+  REGISTER char *p, *end;
+  Lisp_Object sym, fun, tem;
+  char *name;
+
+#ifndef CANNOT_DUMP
+  if (!purify_flag)
+    error ("Snarf-documentation can only be called in an undumped Emacs");
+#endif
+
+  CHECK_STRING (filename);
+
+#ifdef CANNOT_DUMP
+  if (!NILP(Vdoc_directory))
+    {
+      CHECK_STRING (Vdoc_directory);
+      name = (char *) alloca (XSTRING_LENGTH (filename)
+			      + XSTRING_LENGTH (Vdoc_directory)
+			      + 1);
+      strcpy (name, (char *) XSTRING_DATA (Vdoc_directory));
+    }
+  else
+#endif /* CANNOT_DUMP */
+    {
+      name = (char *) alloca (XSTRING_LENGTH (filename) + 14);
+      strcpy (name, "../lib-src/");
+    }
+
+  strcat (name, (char *) XSTRING_DATA (filename));
+
+  fd = open (name, O_RDONLY | OPEN_BINARY, 0);
+  if (fd < 0)
+    report_file_error ("Opening doc string file",
+		       Fcons (build_string (name), Qnil));
+  Vinternal_doc_file_name = filename;
+  filled = 0;
+  pos = 0;
+  while (1)
+    {
+      if (filled < 512)
+	filled += read (fd, &buf[filled], sizeof buf - 1 - filled);
+      if (!filled)
+	break;
+
+      buf[filled] = 0;
+      p = buf;
+      end = buf + (filled < 512 ? filled : filled - 128);
+      while (p != end && *p != '\037') p++;
+      /* p points to ^_Ffunctionname\n or ^_Vvarname\n.  */
+      if (p != end)
+	{
+	  end = strchr (p, '\n');
+	  sym = oblookup (Vobarray, (Bufbyte *) p + 2, end - p - 2);
+	  if (SYMBOLP (sym))
+	    {
+              Lisp_Object offset = make_int (pos + end + 1 - buf);
+	      /* Attach a docstring to a variable */
+	      if (p[1] == 'V')
+		{
+		  /* Install file-position as variable-documentation property
+		     and make it negative for a user-variable
+		     (doc starts with a `*').  */
+		  Lisp_Object old = Fget (sym, Qvariable_documentation, Qzero);
+                  if (!ZEROP (old))
+		    {
+		      weird_doc (sym, GETTEXT ("duplicate"),
+				 GETTEXT ("variable"), pos);
+		      /* In the case of duplicate doc file entries, always
+			 take the later one.  But if the doc is not an int
+			 (a string, say) leave it alone. */
+		      if (!INTP (old))
+			goto weird;
+		    }
+		  Fput (sym, Qvariable_documentation,
+                        ((end[1] == '*')
+                         ? make_int (- XINT (offset))
+                         : offset));
+		}
+	      /* Attach a docstring to a function.
+                 The type determines where the docstring is stored.  */
+	      else if (p[1] == 'F')
+		{
+                  fun = indirect_function (sym,0);
+
+		  if (CONSP (fun) && EQ (XCAR (fun), Qmacro))
+		    fun = XCDR (fun);
+
+                  if (UNBOUNDP (fun))
+		    {
+		      /* May have been #if'ed out or something */
+		      weird_doc (sym, GETTEXT ("not fboundp"),
+				 GETTEXT ("function"), pos);
+		      goto weird;
+		    }
+		  else if (SUBRP (fun))
+		    {
+		      /* Lisp_Subrs have a slot for it.  */
+		      if (XSUBR (fun)->doc)
+			{
+			  weird_doc (sym, GETTEXT ("duplicate"),
+				     GETTEXT ("subr"), pos);
+			  goto weird;
+			}
+		      XSUBR (fun)->doc = (char *) (- XINT (offset));
+		    }
+		  else if (CONSP (fun))
+		    {
+                      /* If it's a lisp form, stick it in the form.  */
+		      tem = XCAR (fun);
+		      if (EQ (tem, Qlambda) || EQ (tem, Qautoload))
+			{
+			  tem = Fcdr (Fcdr (fun));
+			  if (CONSP (tem) &&
+			      INTP (XCAR (tem)))
+			    {
+			      Lisp_Object old = XCAR (tem);
+			      if (!ZEROP (old))
+				{
+				  weird_doc (sym, GETTEXT ("duplicate"),
+					     (EQ (tem, Qlambda)
+					      ? GETTEXT ("lambda")
+					      : GETTEXT ("autoload")),
+					     pos);
+				  /* In the case of duplicate doc file entries,
+				     always take the later one.  But if the doc
+				     is not an int (a string, say) leave it
+				     alone. */
+				  if (!INTP (old))
+				    goto weird;
+				}
+			      XCAR (tem) = offset;
+			    }
+                          else if (!CONSP (tem))
+			    {
+			      weird_doc (sym, GETTEXT ("!CONSP(tem)"),
+					 GETTEXT ("function"), pos);
+			      goto cont;
+			    }
+                          else
+			    {
+			      /* DOC string is a string not integer 0 */
+#if 0
+			      weird_doc (sym, GETTEXT ("!INTP(XCAR(tem))"),
+					 GETTEXT ("function"), pos);
+#endif
+			      goto cont;
+			    }
+                        }
+                      else
+			{
+			  weird_doc (sym, GETTEXT ("not lambda or autoload"),
+				     GETTEXT ("function"), pos);
+			  goto cont;
+			}
+		    }
+		  else if (COMPILED_FUNCTIONP (fun))
+		    {
+                      /* Compiled-Function objects sometimes have
+                         slots for it.  */
+                      struct Lisp_Compiled_Function *f =
+			XCOMPILED_FUNCTION (fun);
+
+		      /* This compiled-function object must have a
+			 slot for the docstring, since we've found a
+			 docstring for it.  Unless there were multiple
+			 definitions of it, and the latter one didn't
+			 have any doc, which is a legal if slightly
+			 bogus situation, so don't blow up. */
+
+                      if (! (f->flags.documentationp))
+			{
+			  weird_doc (sym, GETTEXT ("no doc slot"),
+				     GETTEXT ("bytecode"), pos);
+			  goto weird;
+			}
+		      else
+			{
+			  Lisp_Object old =
+			    compiled_function_documentation (f);
+			  if (!ZEROP (old))
+			    {
+			      weird_doc (sym, GETTEXT ("duplicate"),
+					 GETTEXT ("bytecode"), pos);
+			      /* In the case of duplicate doc file entries,
+				 always take the later one.  But if the doc is
+				 not an int (a string, say) leave it alone. */
+			      if (!INTP (old))
+				goto weird;
+			    }
+			  set_compiled_function_documentation (f, offset);
+			}
+                    }
+                  else
+                    {
+                      /* Otherwise the function is undefined or
+                         otherwise weird.   Ignore it. */
+                      weird_doc (sym, GETTEXT ("weird function"),
+				 GETTEXT ("function"), pos);
+                      goto weird;
+                    }
+                }
+	      else
+                {
+                /* lose: */
+                  error ("DOC file invalid at position %d", pos);
+                weird:
+                  /* goto lose */;
+                }
+            }
+	}
+    cont:
+      pos += end - buf;
+      filled -= end - buf;
+      memmove (buf, end, filled);
+    }
+  close (fd);
+  return Qnil;
+}
+
+
+#if 1	/* Don't warn about functions whose doc was lost because they were
+	   wrapped by advice-freeze.el... */
+static int
+kludgily_ignore_lost_doc_p (Lisp_Object sym)
+{
+# define kludge_prefix "ad-Orig-"
+  struct Lisp_String *name = XSYMBOL (sym)->name;
+  return (string_length (name) > (Bytecount) (sizeof (kludge_prefix)) &&
+	  !strncmp ((char *) string_data (name), kludge_prefix,
+		    sizeof (kludge_prefix) - 1));
+# undef kludge_prefix
+}
+#else
+# define kludgily_ignore_lost_doc_p(sym) 0
+#endif
+
+
+static int
+verify_doc_mapper (Lisp_Object sym, void *arg)
+{
+  Lisp_Object closure = *(Lisp_Object *)arg;
+
+  if (!NILP (Ffboundp (sym)))
+    {
+      int doc = 0;
+      Lisp_Object fun = XSYMBOL (sym)->function;
+      if (CONSP (fun) &&
+	  EQ (XCAR (fun), Qmacro))
+	fun = XCDR (fun);
+
+      if (SUBRP (fun))
+	doc = (EMACS_INT) XSUBR (fun)->doc;
+      else if (SYMBOLP (fun))
+	doc = -1;
+      else if (KEYMAPP (fun))
+	doc = -1;
+      else if (CONSP (fun))
+	{
+	  Lisp_Object tem = XCAR (fun);
+	  if (EQ (tem, Qlambda) || EQ (tem, Qautoload))
+	    {
+	      doc = -1;
+	      tem = Fcdr (Fcdr (fun));
+	      if (CONSP (tem) &&
+		  INTP (XCAR (tem)))
+		doc = XINT (XCAR (tem));
+	    }
+	}
+      else if (COMPILED_FUNCTIONP (fun))
+	{
+          struct Lisp_Compiled_Function *f = XCOMPILED_FUNCTION (fun);
+          if (! (f->flags.documentationp))
+            doc = -1;
+          else
+            {
+              Lisp_Object tem = compiled_function_documentation (f);
+              if (INTP (tem))
+                doc = XINT (tem);
+            }
+	}
+
+      if (doc == 0 && !kludgily_ignore_lost_doc_p (sym))
+	{
+	  message ("Warning: doc lost for function %s.",
+		   string_data (XSYMBOL (sym)->name));
+	  XCDR (closure) = Qt;
+	}
+    }
+  if (!NILP (Fboundp (sym)))
+    {
+      Lisp_Object doc = Fget (sym, Qvariable_documentation, Qnil);
+      if (ZEROP (doc))
+	{
+	  message ("Warning: doc lost for variable %s.",
+		   string_data (XSYMBOL (sym)->name));
+	  XCDR (closure) = Qt;
+	}
+    }
+  return 0; /* Never stop */
+}
+
+DEFUN ("Verify-documentation", Fverify_documentation, 0, 0, 0, /*
+Used to make sure everything went well with Snarf-documentation.
+Writes to stderr if not.
+*/
+       ())
+{
+  Lisp_Object closure = Fcons (Qnil, Qnil);
+  struct gcpro gcpro1;
+  GCPRO1 (closure);
+  map_obarray (Vobarray, verify_doc_mapper, &closure);
+  if (!NILP (Fcdr (closure)))
+    message ("\n"
+"This is usually because some files were preloaded by loaddefs.el or\n"
+"site-load.el, but were not passed to make-docfile by Makefile.\n");
+  UNGCPRO;
+  return NILP (Fcdr (closure)) ? Qt : Qnil;
+}
+
+
+DEFUN ("substitute-command-keys", Fsubstitute_command_keys, 1, 1, 0, /*
+Substitute key descriptions for command names in STRING.
+Return a new string which is STRING with substrings of the form \\=\\[COMMAND]
+replaced by either:  a keystroke sequence that will invoke COMMAND,
+or "M-x COMMAND" if COMMAND is not on any keys.
+Substrings of the form \\=\\{MAPVAR} are replaced by summaries
+\(made by describe-bindings) of the value of MAPVAR, taken as a keymap.
+Substrings of the form \\=\\<MAPVAR> specify to use the value of MAPVAR
+as the keymap for future \\=\\[COMMAND] substrings.
+\\=\\= quotes the following character and is discarded;
+thus, \\=\\=\\=\\= puts \\=\\= into the output, and \\=\\=\\=\\[ puts \\=\\[ into the output.
+*/
+       (str))
+{
+  /* This function can GC */
+  Bufbyte *buf;
+  int changed = 0;
+  REGISTER Bufbyte *strdata;
+  REGISTER Bufbyte *bufp;
+  Bytecount strlength;
+  Bytecount idx;
+  Bytecount bsize;
+  Bufbyte *new;
+  Lisp_Object tem;
+  Lisp_Object keymap;
+  Bufbyte *start;
+  Bytecount length;
+  Lisp_Object name;
+  struct gcpro gcpro1, gcpro2, gcpro3, gcpro4;
+
+  if (NILP (str))
+    return Qnil;
+
+  CHECK_STRING (str);
+  tem = Qnil;
+  keymap = Qnil;
+  name = Qnil;
+  GCPRO4 (str, tem, keymap, name);
+
+  /* There is the possibility that the string is not destined for a
+     translating stream, and it could be argued that we should do the
+     same thing here as in Fformat(), but there are very few times
+     when this will be the case and many calls to this function
+     would have to have `gettext' calls added. (I18N3) */
+  str = LISP_GETTEXT (str);
+
+  /* KEYMAP is either nil (which means search all the active keymaps)
+     or a specified local map (which means search just that and the
+     global map).  If non-nil, it might come from Voverriding_local_map,
+     or from a \\<mapname> construct in STR itself..  */
+#if 0 /* FSFmacs */
+  /* This is really weird and garbagey.  If keymap is nil and there's
+     an overriding-local-map, `where-is-internal' will correctly note
+     this, so there's no reason to do it here.  Maybe FSFmacs
+     `where-is-internal' is broken. */
+  /*
+  keymap = current_kboard->Voverriding_terminal_local_map;
+  if (NILP (keymap))
+    keymap = Voverriding_local_map;
+  */
+#endif
+
+  strlength = XSTRING_LENGTH (str);
+  bsize = 1 + strlength;
+  buf = (Bufbyte *) xmalloc (bsize);
+  bufp = buf;
+
+  /* Have to reset strdata every time GC might be called */
+  strdata = XSTRING_DATA (str);
+  for (idx = 0; idx < strlength; )
+    {
+      Bufbyte *strp = strdata + idx;
+
+      if (strp[0] != '\\')
+	{
+	  /* just copy other chars */
+	  /* As it happens, this will work with Mule even if the
+	     character quoted is multi-byte; the remaining multi-byte
+	     characters will just be copied by this loop. */
+	  *bufp++ = *strp;
+	  idx++;
+	}
+      else switch (strp[1])
+	{
+	default:
+	  {
+	    /* just copy unknown escape sequences */
+	    *bufp++ = *strp;
+	    idx++;
+	    break;
+	  }
+	case '=':
+	  {
+	    /* \= quotes the next character;
+	       thus, to put in \[ without its special meaning, use \=\[.  */
+	    /* As it happens, this will work with Mule even if the
+	       character quoted is multi-byte; the remaining multi-byte
+	       characters will just be copied by this loop. */
+	    changed = 1;
+	    *bufp++ = strp[2];
+	    idx += 3;
+	    break;
+	  }
+	case '[':
+	  {
+	    changed = 1;
+	    idx += 2;		/* skip \[ */
+	    strp += 2;
+	    start = strp;
+
+	    while ((idx < strlength)
+		   && *strp != ']')
+	      {
+		strp++;
+		idx++;
+	      }
+	    length = strp - start;
+	    idx++;		/* skip ] */
+
+	    tem = Fintern (make_string (start, length), Qnil);
+	    tem = Fwhere_is_internal (tem, keymap, Qt, Qnil, Qnil);
+
+#if 0 /* FSFmacs */
+	  /* Disregard menu bar bindings; it is positively annoying to
+	     mention them when there's no menu bar, and it isn't terribly
+	     useful even when there is a menu bar.  */
+	  if (!NILP (tem))
+	    {
+	      firstkey = Faref (tem, Qzero);
+	      if (EQ (firstkey, Qmenu_bar))
+		tem = Qnil;
+	    }
+#endif
+
+	    if (NILP (tem))	/* but not on any keys */
+	      {
+		new = (Bufbyte *) xrealloc (buf, bsize += 4);
+		bufp += new - buf;
+		buf = new;
+		memcpy (bufp, "M-x ", 4);
+		bufp += 4;
+		goto subst;
+	      }
+	    else
+	      {			/* function is on a key */
+		tem = Fkey_description (tem);
+		goto subst_string;
+	      }
+	  }
+	case '{':
+	case '<':
+	  {
+	    /* ### jump to label `subst_string|subst' crosses
+               initialization of `buffer|_buf' */
+	    Lisp_Object buffer;
+	    struct buffer *buf_;
+
+	    buffer = Fget_buffer_create (QSsubstitute);
+	    buf_ = XBUFFER (buffer);
+
+	    Fbuffer_disable_undo (buffer);
+	    Ferase_buffer (buffer);
+
+	    /* \{foo} is replaced with a summary of keymap (symbol-value foo).
+	       \<foo> just sets the keymap used for \[cmd].  */
+	    changed = 1;
+	    idx += 2;		/* skip \{ or \< */
+	    strp += 2;
+	    start = strp;
+
+	    while ((idx < strlength)
+		   && *strp != '}' && *strp != '>')
+	      {
+		strp++;
+		idx++;
+	      }
+	    length = strp - start;
+	    idx++;		/* skip } or > */
+
+	    /* Get the value of the keymap in TEM, or nil if undefined.
+	       Do this while still in the user's current buffer
+	       in case it is a local variable.  */
+	    name = Fintern (make_string (start, length), Qnil);
+	    tem = Fboundp (name);
+	    if (! NILP (tem))
+	      {
+		tem = Fsymbol_value (name);
+		if (! NILP (tem))
+		  tem = get_keymap (tem, 0, 1);
+	      }
+
+	    if (NILP (tem))
+	      {
+		char boof[255], *b = boof;
+		*b++ = '\n';
+		/* #### This sprintf() is potentially dangerous!  */
+		sprintf (b, GETTEXT (
+		"Uses keymap \"%s\", which is not currently defined."),
+			 (char *) XSTRING_DATA (Fsymbol_name (name)));
+		b += strlen (b);
+		*b++ = '\n';
+		*b++ = 0;
+		buffer_insert_c_string (buf_, boof);
+
+		if (start[-1] == '<') keymap = Qnil;
+	      }
+	    else if (start[-1] == '<')
+	      keymap = tem;
+	    else
+	      describe_map_tree (tem, 1, Qnil, Qnil, 0, buffer);
+
+	    tem = make_string_from_buffer (buf_, BUF_BEG (buf_),
+					   BUF_Z (buf_) - BUF_BEG (buf_));
+	    Ferase_buffer (buffer);
+	    goto subst_string;
+
+	  subst_string:
+	    start = XSTRING_DATA (tem);
+	    length = XSTRING_LENGTH (tem);
+	  subst:
+	    bsize += length;
+	    new = (Bufbyte *) xrealloc (buf, bsize);
+	    bufp += new - buf;
+	    buf = new;
+	    memcpy (bufp, start, length);
+	    bufp += length;
+
+	    /* Reset STRDATA in case gc relocated it.  */
+	    strdata = XSTRING_DATA (str);
+
+	    break;
+	  }
+	}
+    }
+
+  if (changed)			/* don't bother if nothing substituted */
+    tem = make_string (buf, bufp - buf);
+  else
+    tem = str;
+  xfree (buf);
+  UNGCPRO;
+  return tem;
+}
+
+
+/************************************************************************/
+/*                            initialization                            */
+/************************************************************************/
+
+void
+syms_of_doc (void)
+{
+  DEFSUBR (Fdocumentation);
+  DEFSUBR (Fdocumentation_property);
+  DEFSUBR (Fsnarf_documentation);
+  DEFSUBR (Fverify_documentation);
+  DEFSUBR (Fsubstitute_command_keys);
+}
+
+void
+vars_of_doc (void)
+{
+  DEFVAR_LISP ("internal-doc-file-name", &Vinternal_doc_file_name /*
+Name of file containing documentation strings of built-in symbols.
+*/ );
+  Vinternal_doc_file_name = Qnil;
+
+  QSsubstitute = build_string (" *substitute*");
+  staticpro (&QSsubstitute);
+}