master: sb-manual: implement concept indexing

melisgl via Sbcl-commits <[email protected]> Mon, 29 Jun 2026 12:20:16 +0000
Newsgroups gmane.lisp.steel-bank.cvs
Message-ID <[email protected]>
The branch "master" has been updated in SBCL:
       via  40658bfd6a61376775439289abeb6489ab42802f (commit)
      from  2e9dc0721f8dcf85ce539754d039dd9a038ca1cb (commit)

- Log -----------------------------------------------------------------
commit 40658bfd6a61376775439289abeb6489ab42802f
Author: Gabor Melis <[email protected]>
Date:   Sun Jun 14 01:30:28 2026 +0200

    sb-manual: implement concept indexing
---
 contrib/sb-aclrepl/manual.lisp        |   2 +-
 contrib/sb-aclrepl/sb-aclrepl.texinfo |   2 +
 contrib/sb-manual/doc/concepts.lisp   |   5 +
 contrib/sb-manual/doc/start-stop.lisp |   5 +-
 contrib/sb-manual/markdown.lisp       | 131 ++++++++++++++++--------
 contrib/sb-manual/pax.lisp            | 187 +++++++++++++++++++++++++++++-----
 contrib/sb-manual/sb-manual.asd       |   3 +-
 contrib/sb-manual/texinfo.lisp        |   5 +-
 doc/manual/backmatter.texinfo         |   6 ++
 doc/manual/sbcl.texinfo               |   1 +
 doc/manual/start-stop.texinfo         |   7 +-
 doc/manual/streams.texinfo            |   4 +-
 src/pcl/gray-streams.lisp             |   4 +-
 13 files changed, 279 insertions(+), 83 deletions(-)

diff --git a/contrib/sb-aclrepl/manual.lisp b/contrib/sb-aclrepl/manual.lisp
index e7ac49126..860c83f9f 100644
--- a/contrib/sb-aclrepl/manual.lisp
+++ b/contrib/sb-aclrepl/manual.lisp
@@ -1,6 +1,6 @@
 (in-package :sb-manual)
 
-(defsection @sb-aclrepl (:title "sb-aclrepl")
+(defsection @sb-aclrepl (:title "sb-aclrepl" :concepts (~repl))
   "The `SB-ACLREPL` module offers an Allegro CL-style
   Read-Eval-Print Loop for SBCL, with integrated inspector. Adding a
   debugger interface is planned.
diff --git a/contrib/sb-aclrepl/sb-aclrepl.texinfo b/contrib/sb-aclrepl/sb-aclrepl.texinfo
index 81f4a343f..a3af2c01a 100644
--- a/contrib/sb-aclrepl/sb-aclrepl.texinfo
+++ b/contrib/sb-aclrepl/sb-aclrepl.texinfo
@@ -1,6 +1,8 @@
 @c Generated by the sb-manual contrib. Do not edit.
 
 @node sb aclrepl
+@cindex Read-Eval-Print Loop
+@cindex REPL
 @section sb-aclrepl
 
 @menu
diff --git a/contrib/sb-manual/doc/concepts.lisp b/contrib/sb-manual/doc/concepts.lisp
new file mode 100644
index 000000000..0d6ee4cc9
--- /dev/null
+++ b/contrib/sb-manual/doc/concepts.lisp
@@ -0,0 +1,5 @@
+(in-package :sb-manual)
+
+(define-concept ~repl (:keys ("Read-Eval-Print Loop" "REPL")))
+
+(define-concept @repl (:title "REPL" :keys (~repl)))
diff --git a/contrib/sb-manual/doc/start-stop.lisp b/contrib/sb-manual/doc/start-stop.lisp
index aa29264a0..d884f6393 100644
--- a/contrib/sb-manual/doc/start-stop.lisp
+++ b/contrib/sb-manual/doc/start-stop.lisp
@@ -327,8 +327,9 @@
 
       The user initialization file is intended for personal
       customizations, such as loading certain modules at startup,
-      defining convenience functions to use in the REPL, handling
-      automatic recompilation of FASLs (see @FASL-FORMAT), etc.
+      defining convenience functions to use in the @REPL,
+      handling automatic recompilation of FASLs (see @FASL-FORMAT),
+      etc.
 
   Neither initialization file is required.")
 
diff --git a/contrib/sb-manual/markdown.lisp b/contrib/sb-manual/markdown.lisp
index b55b6d9b3..9e8505fe2 100644
--- a/contrib/sb-manual/markdown.lisp
+++ b/contrib/sb-manual/markdown.lisp
@@ -5,10 +5,13 @@
 ;;;; the COPYING file for more information.
 ;;;;
 ;;;; Written by Rudi Schlatte <[email protected]>, mangled by
-;;;; Nikodemus Siivola. Brought closer to Markdown by Gabor Melis.
+;;;; Nikodemus Siivola. Brought closer to Markdown, extended with
+;;;; section linking and concept indexing by Gabor Melis.
 
 (in-package :sb-manual)
 
+(defvar *concept-keys-to-prepend*)
+
 ;;; MARKDOWN-TO-TEXINFO converts a strict subset of Markdown to
 ;;; Texinfo. It also codifies (marks up as code) and downcases
 ;;; uppercase symbols (those that actually exist in the image), and
@@ -118,8 +121,6 @@
 ;;; Linking
 ;;; -------
 ;;;
-;;; - Section references: @SECTION-NAME -> @ref{section name}
-;;;
 ;;; - <http...> -> @url{http...}
 ;;;
 ;;; - [label](uri) -> @uref{uri, label}
@@ -131,7 +132,16 @@
 ;;;     FUNCTION class only while FUNCTION links to both the class and
 ;;;     macro.
 ;;;
-;;; FIXME:
+;;; - SECTION references (see DEFSECTION):
+;;;
+;;;     @SECTION-NAME -> @ref{<section name>}
+;;;
+;;; - CONCEPT references (see DEFINE-CONCEPT):
+;;;
+;;;     - pure concept: @CONCEPT-NAME -> "" (no output)
+;;;     - titled concept: @CONCEPT-NAME -> <title>
+;;;
+;;; TODO:
 ;;;
 ;;; - Maybe implement glossary-terms (for books, "safe type", etc).
 (defun markdown-to-texinfo (string &optional lambda-list)
@@ -142,28 +152,33 @@
     (declare (special *texinfo-local-variables*))
     (flet ((flush-paragraph ()
              (when current-paragraph
-               (write-string (process-inline-markdown
-                              (format nil "~{~A~^~%~}"
-                                      (nreverse current-paragraph))))
+               (let* ((*concept-keys-to-prepend* ())
+                      (string (process-inline-markdown
+                               (format nil "~{~A~^~%~}"
+                                       (nreverse current-paragraph)))))
+                 (write-concept-keys *concept-keys-to-prepend* t)
+                 (write-string string))
                (terpri)
                (setf current-paragraph nil))))
       (loop while (< line-number (length lines))
             for line = (svref lines line-number)
-            do (multiple-value-bind (count collected)
-                   (parse-markdown-blocks lines line-number 0)
-                 (cond
-                   (count
-                    (flush-paragraph)
-                    (dolist (c collected)
-                      (write-line c))
-                    (incf line-number count))
-                   ((blankp line)
-                    (flush-paragraph)
-                    (write-line line)
-                    (incf line-number))
-                   (t
-                    (push line current-paragraph)
-                    (incf line-number)))))
+            do (let ((*concept-keys-to-prepend* ()))
+                 (multiple-value-bind (count collected)
+                     (parse-markdown-block lines line-number 0)
+                   (cond
+                     (count
+                      (flush-paragraph)
+                      (write-concept-keys *concept-keys-to-prepend* t)
+                      (dolist (c collected)
+                        (write-line c))
+                      (incf line-number count))
+                     ((blankp line)
+                      (flush-paragraph)
+                      (write-line line)
+                      (incf line-number))
+                     (t
+                      (push line current-paragraph)
+                      (incf line-number))))))
       (flush-paragraph))))
 
 
@@ -330,7 +345,7 @@
 (defparameter *word-characters*
   (format nil "abcdefghijklmnopqrstuvwxyz~
                ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~
-               *@:-+&=<>#'"))
+               *~~@:-+&=<>#'"))
 
 (defparameter *word-delimiters* " ',.!?;()[]{}")
 
@@ -402,8 +417,8 @@
 
 (unwind-protect
      (progn
-       (defsection @test-section ())
-       (defsection @test5 ())
+       (defsection @test-section (:title "test section"))
+       (defsection @test5 (:title "Test5"))
        (assert (equal (locate-symbols "PRINT") '((0 5))))
        (assert (equal (locate-symbols "CL:PRINT") '((0 8))))
        (assert (equal (locate-symbols "*FEATURES*") '((0 10))))
@@ -430,19 +445,46 @@
     (let ((last 0))
       (dolist (symbol/index (locate-symbols line))
         ;; Flush unwritten text since the end of the previous symbol.
-        (write-string (escape-texinfo (subseq line last (first symbol/index)))
+        (write-string (escape-texinfo (subseq line last
+                                              (first symbol/index)))
                       result)
         (let* ((symbol-name (apply #'subseq line symbol/index))
                (symbol (read-from-string symbol-name)))
-          (if (section-name-p symbol)
-              (format result "@ref{~A}"
-                      (texinfo-node-id (symbol-value symbol)))
-              ;; We could use for @var{} if (HAS-LOCAL-REFERENCE-P SYMBOL).
-              (format result "@code{~A}" (escape-texinfo
-                                          (maybe-downcase symbol-name)))))
+          (cond ((doc-name-p symbol :section)
+                 (format result "@ref{~A}"
+                         (texinfo-node-id (symbol-value symbol))))
+                ((doc-name-p symbol :concept)
+                 (let* ((concept (symbol-value symbol))
+                        (title (doctitle concept)))
+                   (when title
+                     (format result "~A" (escape-texinfo title)))
+                   (setq *concept-keys-to-prepend*
+                         (append *concept-keys-to-prepend*
+                                 (multiplexing-concept-keys concept)))))
+                (t
+                 ;; We could use for @var{} if
+                 ;; (HAS-LOCAL-REFERENCE-P SYMBOL).
+                 (format result "@code{~A}"
+                         (escape-texinfo (maybe-downcase symbol-name))))))
         (setf last (second symbol/index)))
       (write-string (escape-texinfo (subseq line last)) result))))
 
+(defun write-concept-keys (keys stream)
+  (dolist (key keys)
+    (typecase key
+      (list
+       ;; We don't use @subentry because with it Texinfo always
+       ;; presents it as as hierarchical list even if it has only one
+       ;; branch.
+       (format stream "~&@cindex~{ ~A~}~%" key))
+      (symbol
+       (assert (and (boundp key)
+                    (typep (symbol-value key) (dummy 'concept)))
+               () "Variable ~S does not hold a concept." key)
+       (write-concept-keys (concept-keys (symbol-value key)) stream))
+      (t
+       (format stream "~&@cindex ~A~%" key)))))
+
 (defvar *downcase-uppercase-code* t)
 
 (defun maybe-downcase (string)
@@ -460,18 +502,17 @@
               () "Section name ~S contains special texinfo characters." name)
       (substitute #\Space #\- (string-downcase name)))))
 
-(defun section-name-p (symbol)
-  (when (boundp symbol)
-    (if *using-pax*
-        (typep (symbol-value symbol) (dummy 'section))
-        (let ((value (symbol-value symbol)))
-          (and (listp value)
-               (listp (first value))
-               (eq (caar value) :%pax-lazy-section))))))
+(defun doc-name-p (symbol kind)
+  (if *using-pax*
+      (and (boundp symbol)
+           (typep (symbol-value symbol) (dummy (ecase kind
+                                                 (:section 'section)
+                                                 (:concept 'concept)))))
+      (lazy-doc-name-p symbol kind)))
 
 (when (and (not *using-pax*)
            *downcase-uppercase-code*)
-  (defsection @test-section ())
+  (defsection @test-section (:title "Test Section"))
   (unwind-protect
        (progn
          (assert (equal (codify-and-link "@TEST-SECTION") "@ref{test section}"))
@@ -832,7 +873,7 @@
                      ((and indent (>= indent child-base))
                       (flush-paragraph)
                       (multiple-value-bind (sub-consumed sub-result)
-                          (parse-markdown-blocks lines line-number child-base)
+                          (parse-markdown-block lines line-number child-base)
                         (if sub-consumed
                             (progn
                               (setf result (append (reverse sub-result) result))
@@ -859,16 +900,16 @@
 
 ;;; Parse the line at INDEX in LINES as a Markdown block. Return the
 ;;; number of lines consumed and the parse.
-(defun parse-markdown-blocks (lines index base-indent)
+(defun parse-markdown-block (lines index base-indent)
   (let ((line (svref lines index)))
     (multiple-value-bind (n-lines-consumed result)
         (collect-fenced-code lines index base-indent)
       (when n-lines-consumed
-        (return-from parse-markdown-blocks (values n-lines-consumed result))))
+        (return-from parse-markdown-block (values n-lines-consumed result))))
     (multiple-value-bind (n-lines-consumed result)
         (collect-blockquote lines index base-indent)
       (when n-lines-consumed
-        (return-from parse-markdown-blocks (values n-lines-consumed result))))
+        (return-from parse-markdown-block (values n-lines-consumed result))))
     (cond
       ((maybe-itemize-offset line)
        (collect-markdown-itemize lines index (maybe-itemize-offset line)))
diff --git a/contrib/sb-manual/pax.lisp b/contrib/sb-manual/pax.lisp
index ba4a2f05c..441bf8fad 100644
--- a/contrib/sb-manual/pax.lisp
+++ b/contrib/sb-manual/pax.lisp
@@ -42,6 +42,7 @@
     (setf-function :dref)
     (setf-generic-function :dref)
     (section :pax)
+    (concept :pax)
     (clhs :pax)
     (docstring :dref)))
 
@@ -53,10 +54,19 @@
 (defvar *definition-to-docstring-package*)
 (defvar *package-to-docstring-package*)
 
-(defun resolve-lazy-section (&rest args)
+(defun resolve-lazy-doc (&rest args)
   (declare (ignore args))
   (use-pax))
 
+(defun lazy-doc-name-p (symbol &optional kind)
+  (when (boundp symbol)
+    (let ((value (symbol-value symbol)))
+      (and (listp value)
+           (listp (first value))
+           (eq (caar value) :%pax-lazy-doc)
+           (or (null kind)
+               (eq (third (first value)) kind))))))
+
 (defun use-pax ()
   "Ensure that exported variables are `PAX:SECTION`s.
   It is an error if the `MGL-PAX` library is not loaded.
@@ -72,37 +82,41 @@
   The latter feature requires v0.4.12 of PAX. See the `MGL-PAX`
   asdf:system."
   (unless *using-pax*
-    (assert (find-package '#:mgl-pax))
+    (assert (find-package '#:mgl-pax) ()
+            "The MGL-PAX package does not exist. Load PAX first.")
     ;; Replace dummies with the real symbols.
     (loop for (name package) in (append *dummies* *extra-dummies*)
           do (let ((new-symbol (read-from-string
-                                (format nil "~A:~A" package name))))
+                                (format nil "~A::~A" package name))))
                (when (and (fboundp new-symbol)
                           (null (macro-function new-symbol)))
                  (setf (fdefinition name) (fdefinition new-symbol)))
                (shadowing-import new-symbol :sb-manual)))
-    ;; Arrange for that only SECTIONs will be exported by
-    ;; PAX:DEFSECTION.
+    ;; Ensure PAX:DEFSECTION exports only SECTIONs.
     (eval-string
      "(defmethod pax:exportable-reference-p
           ((package (eql (find-package 'sb-manual)))
            symbol locative-type locative-args)
         (eq locative-type 'section))")
-    ;; Reevaluate DEFSECTION forms with PAX.
-    (do-external-symbols (symbol :sb-manual)
-      (when (and (char= #\@ (aref (symbol-name symbol) 0))
-                 (boundp symbol))
-        (let ((value (symbol-value symbol)))
-          (assert (and (consp value) (consp (car value ))
-                       (eq (caar value) :%pax-lazy-section)))
-          (let ((source-location
-                  (sb-int:info :source-location :variable symbol)))
-            (eval `(,(read-from-string "pax:defsection")
-                    ,@(subst-extras (rest (second value)))))
-            (setf (sb-int:info :source-location :variable symbol)
-                  source-location)))))
+    ;; Reevaluate DEFSECTION and DEFINE-CONCEPT forms with PAX.
+    (do-symbols (symbol :sb-manual)
+      (when (lazy-doc-name-p symbol)
+        (let ((value (symbol-value symbol))
+              (source-location
+                (sb-int:info :source-location :variable symbol)))
+          (eval (subst-dummies (second value)))
+          (setf (sb-int:info :source-location :variable symbol)
+                source-location))))
     (convert-docstring-package-overrides-to-pax)
-    ;; FIXME: register doc?
+    (eval-string
+     "(pax:register-doc-in-pax-world
+       'sb-manual:@sbcl-manual @sbcl-manual
+       `((:objects
+          (, @sbcl-manual)
+          :source-uri-fn ,(pax:make-git-source-uri-fn
+                           nil \"https://github.com/sbcl/sbcl\"
+                           :git-root (asdf:system-relative-pathname
+                                      :sb-manual \"../../../\")))))")
     (setq *using-pax* t)))
 
 ;;; Convert *DEFINITION-TO-DOCSTRING-PACKAGE* to
@@ -114,9 +128,9 @@
             "(setf (dref-ext:definition-property (dref:xref '~S '~S)~
                                                  'docstring)
                    (list nil (find-package ~S)))"
-            name (subst-extras locative) package))
+            name (subst-dummies locative) package))
   (setq *definition-to-docstring-package*
-        (subst-extras *definition-to-docstring-package*))
+        (subst-dummies *definition-to-docstring-package*))
   (loop for (from-package to-package) in *package-to-docstring-package*
         do (eval-format
             "(setf (dref-ext:definition-property `(:package ,(find-package ~S))
@@ -132,21 +146,54 @@
   (let ((*package* (find-package :sb-manual)))
     (eval (read-from-string (apply #'format nil format-control format-args)))))
 
-(defun subst-extras (tree)
+(defun subst-dummies (tree)
   (let ((new-tree tree))
     (loop
-      for (name package) in *extra-dummies*
-      do (let ((new-name (read-from-string (format nil "~A:~A" package name))))
+      for (name package) in (append *dummies* *extra-dummies*)
+      do (let ((new-name (read-from-string
+                          (format nil "~A::~A" package name))))
            (setq new-tree (subst new-name name new-tree))))
     new-tree))
 
 
+;;; The main job DEFSECTION is to associate a title and a list of
+;;; "entries" with a name. An entry is either a docstring or a
+;;; reference to a definition of the form (<NAME> <DEFINITION-TYPE>).
+;;;
+;;; Concepts and index keys may also be used in DEFSECTION forms:
+;;;
+;;;     (defsection @the-repl (:title "The REPL" :concepts (@repl))
+;;;        "The REPL is ...")
+;;;
+;;; When processed, this becomes
+;;;
+;;;    @cindex Read-Eval-Print Loop
+;;;    @cindex REPL
+;;;    @node the repl
+;;;    @section The REPL
+;;;    The REPL is ...
+;;;
+;;; Note that it doesn't matter in :KEYS of DEFSECTION whether a
+;;; concept is pure or titled.
+;;;
+;;; Note that referencing sections in docstrings does not cause
+;;; indexing. This is due to examples like
+;;;
+;;;     We now move on to the next section, @XXX.
+;;;     See @XXX, for other considerations.
+;;;
+;;; In general, mentioning and linking a section is not the same as
+;;; being about it, and only the latter deserves a concept index
+;;; entry.
+;;;
+;;; See also PAX:DEFSECTION.
 (defmacro-dummy (defsection pax)
-                (name (&key (package *package*) (export t) title)
+                (name (&key (package *package*) (export t) title concepts)
                       &body entries)
   (let ((value
-          `((:%pax-lazy-section ,name resolve-lazy-section)
-            (defsection ,name (:package ,package :export ,export :title ,title)
+          `((:%pax-lazy-doc ,name :section resolve-lazy-doc)
+            (defsection ,name (:package ,package :export ,export :title ,title
+                               :concepts ,concepts)
               ,@entries))))
     `(progn
        (defparameter ,name ',value)
@@ -159,6 +206,9 @@
 (defun-dummy (section-title :pax) (section)
   (getf (third (second section)) :title))
 
+(defun-dummy (concept-keys :pax) (section)
+  (resolve-concept-symbols (getf (third (second section)) :concepts)))
+
 (defun-dummy (section-package :pax) (section)
   (find-package (getf (third (second section)) :package)))
 
@@ -180,3 +230,86 @@
 
 (defun-dummy (xref-locative-type :dref) (xref)
   (first (sb-c::ensure-list (second xref))))
+
+
+;;; DEFINE-CONCEPT names a set of index keys. When MARKDOWN-TO-TEXINFO
+;;; encounters the name of a concept in a docstring, it emits Texinfo
+;;; `@cindex' lines for its keys. There are two kinds of concepts.
+;;;
+;;; When a `pure' concept (that has no title) is processed in a
+;;; docstring by MARKDOWN-TO-TEXINFO, it produces no visible output,
+;;; but it emits Texinfo @cindex lines for its KEYS. It is thus _not_
+;;; part of the normal flow of text.
+;;;
+;;;     (define-concept ~repl (:keys ("Read-Eval-Print Loop" "REPL")))
+;;;
+;;; Example use:
+;;;
+;;;     The REPL ~REPL is interactive.
+;;;
+;;; This becomes
+;;;
+;;;     @cindex Read-Eval-Print Loop
+;;;     @cindex REPL
+;;;     The REPL  is interactive.
+;;;
+;;; When a `titled' concept is processed, it is replaced by its title.
+;;; Use this in the normal flow of text like section names. Note that
+;;; other concepts can be referenced in KEYS. The definition below is
+;;; the titled synonym of ~REPL.
+;;;
+;;;     (define-concept @repl (:title "REPL" :keys (~repl)))
+;;;
+;;; Example use:
+;;;
+;;;     The @REPL is interactive.
+;;;
+;;; This becomes
+;;;
+;;;     @cindex Read-Eval-Print Loop
+;;;     @cindex REPL
+;;;     The REPL is interactive.
+;;;
+;;; By convention, names of pure concepts start with #\~, while those
+;;; of titled concepts start with the usual #\@. The tilde is to draw
+;;; attention to that the name is not part of the normal text.
+;;;
+;;; Finally, index keys can be hierarchical:
+;;;
+;;;     (define-concept ~defining-macros (:keys (("defining" "macros")
+;;;                                              ("macros," "defining"))))
+;;;
+;;; This is processed into
+;;;
+;;;     @cindex defining @subentry macros
+;;;     @cindex macros, @subentry defining
+;;;
+;;; Note Texinfo's @sortas is not supported yet. PAX implements that
+;;; by allowing any subkey string to be a (<NAME> . <SORT-AS-STRING>)
+;;; cons.
+;;;
+;;; See also PAX:DEFINE-CONCEPT.
+(defmacro-dummy (define-concept pax) (name (&key title keys))
+  (let ((value
+          `((:%pax-lazy-doc ,name :concept resolve-lazy-doc)
+            (define-concept ,name (:title ,title :keys ,keys)))))
+    `(defparameter ,name ',value)))
+
+(defun-dummy (multiplexing-concept-keys :pax) (concept)
+  (resolve-concept-symbols (getf (third (second concept)) :keys)))
+
+(defun-dummy (doctitle :pax) (concept)
+  (getf (third (second concept)) :title))
+
+(defun resolve-concept-symbols (list)
+  (if (find-if #'symbolp list)
+      (loop for x in list
+            append (if (symbolp x)
+                       (multiplexing-concept-keys (find-concept x))
+                       (list x)))
+      list))
+
+(defun find-concept (name)
+  (if (doc-name-p name :concept)
+      (symbol-value name)
+      (error "Undefined ~S ~S." 'concept name)))
diff --git a/contrib/sb-manual/sb-manual.asd b/contrib/sb-manual/sb-manual.asd
index 6690f63a8..f472cd373 100644
--- a/contrib/sb-manual/sb-manual.asd
+++ b/contrib/sb-manual/sb-manual.asd
@@ -10,7 +10,8 @@
                (:file "manual")
                (:module "doc/"
                 :serial t
-                :components ((:file "sbcl")
+                :components ((:file "concepts")
+                             (:file "sbcl")
                              (:file "support-and-bugs")
                              (:file "intro")
                              (:file "start-stop")
diff --git a/contrib/sb-manual/texinfo.lisp b/contrib/sb-manual/texinfo.lisp
index c05354f24..ba101c61e 100644
--- a/contrib/sb-manual/texinfo.lisp
+++ b/contrib/sb-manual/texinfo.lisp
@@ -128,6 +128,7 @@
   (let ((title (remove-markup (section-title section)))
         (entries (section-entries section)))
     (format t "@node ~A~%" (texinfo-node-id section))
+    (write-concept-keys (concept-keys section) *standard-output*)
     (format t "~A ~A~%~%"
             (ecase depth
               (0 "@top")
@@ -286,4 +287,6 @@
 (generate-texinfo)
 
 #+nil
-(emit-texinfo-for-section @deprecation-declaration)
+(emit-texinfo-for-section @sb-aclrepl)
+#+nil
+(emit-texinfo-for-section @starting-and-stopping)
diff --git a/doc/manual/backmatter.texinfo b/doc/manual/backmatter.texinfo
index 3b8680860..89551b322 100644
--- a/doc/manual/backmatter.texinfo
+++ b/doc/manual/backmatter.texinfo
@@ -1,3 +1,9 @@
+@node concept index
+@comment  node-name,  next,  previous,  up
+@appendix Concept Index
+
+@printindex cp
+
 @node function index
 @comment  node-name,  next,  previous,  up
 @appendix Function and Macro Index
diff --git a/doc/manual/sbcl.texinfo b/doc/manual/sbcl.texinfo
index 27d5d5406..7d22e8867 100644
--- a/doc/manual/sbcl.texinfo
+++ b/doc/manual/sbcl.texinfo
@@ -83,6 +83,7 @@ provided with absolutely no warranty. See the @file{COPYING} and
 
 @menu
 @include sbcl-menu.texinfo
+* Concept Index: concept index.
 * Function and Macro Index: function index.
 * Variable and Constant Index: variable index.
 * Type Index: type index.
diff --git a/doc/manual/start-stop.texinfo b/doc/manual/start-stop.texinfo
index 374a029a5..8a2578bd2 100644
--- a/doc/manual/start-stop.texinfo
+++ b/doc/manual/start-stop.texinfo
@@ -560,6 +560,8 @@ not @code{load}; hence initialization files can be used to set startup
 @code{*package*} and @code{*readtable*}, and for proclaiming a global optimization
 policy.
 
+@cindex Read-Eval-Print Loop
+@cindex REPL
 @itemize
 @item @strong{System Initialization File:} Defaults to @code{$SBCL_HOME/sbclrc},
   or if that doesn't exist to @code{/etc/sbclrc}. Can be overridden with
@@ -576,8 +578,9 @@ installed third party modules, etc.
 
 The user initialization file is intended for personal
 customizations, such as loading certain modules at startup,
-defining convenience functions to use in the REPL, handling
-automatic recompilation of FASLs (see @ref{fasl format}), etc.
+defining convenience functions to use in the REPL,
+handling automatic recompilation of FASLs (see @ref{fasl format}),
+etc.
 @end itemize
 
 Neither initialization file is required.
diff --git a/doc/manual/streams.texinfo b/doc/manual/streams.texinfo
index e9ee58d10..12b05b8be 100644
--- a/doc/manual/streams.texinfo
+++ b/doc/manual/streams.texinfo
@@ -286,7 +286,7 @@ These generic functions are used to implement subclasses of
 Write enough blank space so that the next character will be
 written at the specified column. Returns true if the operation is
 successful, or @code{nil} if it is not supported for this stream. This is
-intended for use by by @code{pprint} and @code{format} ~T. The default method
+intended for use by by @code{pprint} and @code{format} @code{~t}. The default method
 uses @code{stream-line-column} and repeated calls to @code{stream-write-char}
 with a #SPACE character; it returns @code{nil} if @code{stream-line-column}
 returns @code{nil}.
@@ -305,7 +305,7 @@ otherwise. Used by @code{fresh-line}. The default method uses
 Return the column number where the next character
 will be written, or @code{nil} if that is not meaningful for this stream.
 The first column on a line is numbered 0. This function is used in
-the implementation of @code{pprint} and the @code{format} ~T directive. For every
+the implementation of @code{pprint} and the @code{format} @code{~t} directive. For every
 character output stream class that is defined, a method must be
 defined for this function, although it is permissible for it to
 always return @code{nil}.
diff --git a/src/pcl/gray-streams.lisp b/src/pcl/gray-streams.lisp
index 2ebe1c8ff..902896901 100644
--- a/src/pcl/gray-streams.lisp
+++ b/src/pcl/gray-streams.lisp
@@ -229,7 +229,7 @@
    "Return the column number where the next character
   will be written, or NIL if that is not meaningful for this stream.
   The first column on a line is numbered 0. This function is used in
-  the implementation of PPRINT and the FORMAT ~T directive. For every
+  the implementation of PPRINT and the FORMAT `~T` directive. For every
   character output stream class that is defined, a method must be
   defined for this function, although it is permissible for it to
   always return NIL."))
@@ -329,7 +329,7 @@
    "Write enough blank space so that the next character will be
    written at the specified column. Returns true if the operation is
    successful, or NIL if it is not supported for this stream. This is
-   intended for use by by PPRINT and FORMAT ~T. The default method
+   intended for use by by PPRINT and FORMAT `~T`. The default method
    uses STREAM-LINE-COLUMN and repeated calls to STREAM-WRITE-CHAR
    with a #\\SPACE character; it returns NIL if STREAM-LINE-COLUMN
    returns NIL."))

-----------------------------------------------------------------------


hooks/post-receive
-- 
SBCL