Re: master: Make DOCUMENTATION strip markup from SBCL definitions
Stas Boukarev <[email protected]> Tue, 30 Jun 2026 18:14:12 +0300
| Newsgroups | gmane.lisp.steel-bank.cvs,gmane.lisp.steel-bank.devel |
|---|---|
| Message-ID | <CAF63=12bqskHX=c-cHJjts6RL4q3+1C-4AuWWMfAbdzN08B0Dw@mail.gmail.com> |
One factor: doing it before saving the core would save bytes. Can write out all the docstrings in make-target-2, before stripping them. (Or removing, which is done without #+sb-doc) Also means not lugging around the stripping code in the final image, more bytes saved. On Tue, Jun 30, 2026 at 6:08 PM melisgl via Sbcl-commits <[email protected]> wrote: > > The branch "master" has been updated in SBCL: > via 27621b01ca39e50930f68d29c6966400f4274103 (commit) > from 3335c2b15090630b19eb0aaaf5596361182b5a09 (commit) > > - Log ----------------------------------------------------------------- > commit 27621b01ca39e50930f68d29c6966400f4274103 > Author: Gabor Melis <[email protected]> > Date: Tue Jun 30 14:13:06 2026 +0200 > > Make DOCUMENTATION strip markup from SBCL definitions > > ... and normalize the indentation of their docstrings, too. > > Doing this lazily in DOCUMENTATION allows interactive work on > docstrings (e.g. change a docstring, recompile its definition, > generate documentation from the image). I don't think the overhead in > DOCUMENTATION is a factor. > --- > contrib/sb-manual/docstring.lisp | 53 --------- > contrib/sb-manual/markdown.lisp | 17 --- > contrib/sb-manual/package.lisp | 5 +- > contrib/sb-manual/texinfo.lisp | 45 +++---- > src/pcl/documentation.lisp | 247 ++++++++++++++++++++++++++++++++++++++- > 5 files changed, 273 insertions(+), 94 deletions(-) > > diff --git a/contrib/sb-manual/docstring.lisp b/contrib/sb-manual/docstring.lisp > index 455a38b53..3dc8e13c1 100644 > --- a/contrib/sb-manual/docstring.lisp > +++ b/contrib/sb-manual/docstring.lisp > @@ -1,58 +1,5 @@ > (in-package :sb-manual) > > -;;; Before processing Markdown, the docstring indentation is > -;;; normalized by stripping the longest run of leading spaces common > -;;; to all non-blank lines except the first. This is compatible with > -;;; PAX (see PAX::@MARKDOWN-SUPPORT). > -(defun reindent-docstring (docstring) > - (let ((indentation (docstring-indentation docstring))) > - (strip-docstring-indent docstring indentation t))) > - > - > -;;;; Utilities lifted from DRef and MGL-PAX > - > -;;; Return the minimum number of leading spaces in non-blank lines > -;;; after the first. > -(defun docstring-indentation (docstring &key (first-line-special-p t)) > - (let ((n-min-indentation nil)) > - (with-input-from-string (s docstring) > - (loop for i upfrom 0 > - for line = (read-line s nil nil) > - while line > - do (when (and (or (not first-line-special-p) (plusp i)) > - (not (blankp line))) > - (when (or (null n-min-indentation) > - (< (n-leading-spaces line) n-min-indentation)) > - (setq n-min-indentation (n-leading-spaces line)))))) > - (or n-min-indentation 0))) > - > -(defun n-leading-spaces (line) > - (let ((n 0)) > - (loop for i below (length line) > - while (char= (aref line i) #\Space) > - do (incf n)) > - n)) > - > -(defun subseq* (seq start) > - (subseq seq (min (length seq) start))) > - > -(defun strip-docstring-indent (docstring indentation first-line-special-p) > - (with-output-to-string (out) > - (with-input-from-string (s docstring) > - (loop for i upfrom 0 > - do (multiple-value-bind (line missing-newline-p) > - (read-line s nil nil) > - (unless line > - (return)) > - (write-string (if (and first-line-special-p > - (zerop i)) > - line > - (subseq* line indentation)) > - out) > - (unless missing-newline-p > - (terpri out))))))) > - > - > ;;;; Determining the package for parsing docstrings > ;;;; > ;;;; The package for parsing is the package that was in effect when > diff --git a/contrib/sb-manual/markdown.lisp b/contrib/sb-manual/markdown.lisp > index 9e8505fe2..0ca17991e 100644 > --- a/contrib/sb-manual/markdown.lisp > +++ b/contrib/sb-manual/markdown.lisp > @@ -194,23 +194,6 @@ > (t > (cons (car list) (flatten (cdr list)))))) > > -(defun whitespacep (char) > - (find char #(#\tab #\space #\page #\newline #\return))) > - > -;;; Split STRING into a vector of lines. > -(defun string-lines (string) > - (coerce (with-input-from-string (s string) > - (loop for line = (read-line s nil nil) > - while line collect line)) > - 'vector)) > - > -;;; Position of the first non-SPACE character in LINE. > -(defun indentation (line) > - (position-if-not (lambda (c) (char= c #\Space)) line)) > - > -(defun blankp (line) > - (null (indentation line))) > - > (defun flatten-to-string (list) > (format nil "~{~A~^-~}" (flatten list))) > > diff --git a/contrib/sb-manual/package.lisp b/contrib/sb-manual/package.lisp > index 02a7fadc0..25558e083 100644 > --- a/contrib/sb-manual/package.lisp > +++ b/contrib/sb-manual/package.lisp > @@ -2,4 +2,7 @@ > (handler-bind ((sb-int:package-at-variance #'muffle-warning)) > (defpackage :sb-manual > (:use :cl :sb-alien) > - (:export #:use-pax)))) > + (:export #:use-pax) > + (:import-from #:sb-pcl > + #:string-lines #:whitespacep #:indentation #:blankp > + #:reindent-docstring)))) > diff --git a/contrib/sb-manual/texinfo.lisp b/contrib/sb-manual/texinfo.lisp > index ba101c61e..7babc46f0 100644 > --- a/contrib/sb-manual/texinfo.lisp > +++ b/contrib/sb-manual/texinfo.lisp > @@ -10,28 +10,29 @@ > (lambda-list* name locative-type))) > > (defun %docstring (xref) > - (values (let ((name (xref-name xref)) > - (locative-type (xref-locative-type xref))) > - (case locative-type > - ((function variable declaration) > - (documentation name locative-type)) > - ((generic-function) > - (documentation name 'function)) > - ((type class structure condition) > - (documentation name 'type)) > - (t > - (cond ((eq locative-type (dummy 'macro)) > - (documentation (macro-function name) t)) > - ((eq locative-type (dummy 'setf-function)) > - (documentation (fdefinition name) t)) > - ((eq locative-type (dummy 'setf-generic-function)) > - (documentation (fdefinition name) t)) > - (t > - (assert nil () "Unexpected locative type in ~S." > - xref)))))) > - ;; To be compatible with PAX::@PACKAGE-AND-READTABLE, we > - ;; always return a non-NIL package. > - (docstring-package xref))) > + (let ((sb-pcl::*normalize-sbcl-docstrings* nil)) > + (values (let ((name (xref-name xref)) > + (locative-type (xref-locative-type xref))) > + (case locative-type > + ((function variable declaration) > + (documentation name locative-type)) > + ((generic-function) > + (documentation name 'function)) > + ((type class structure condition) > + (documentation name 'type)) > + (t > + (cond ((eq locative-type (dummy 'macro)) > + (documentation (macro-function name) t)) > + ((eq locative-type (dummy 'setf-function)) > + (documentation (fdefinition name) t)) > + ((eq locative-type (dummy 'setf-generic-function)) > + (documentation (fdefinition name) t)) > + (t > + (assert nil () "Unexpected locative type in ~S." > + xref)))))) > + ;; To be compatible with PAX::@PACKAGE-AND-READTABLE, we > + ;; always return a non-NIL package. > + (docstring-package xref)))) > > (defun lambda-list* (name kind) > (case kind > diff --git a/src/pcl/documentation.lisp b/src/pcl/documentation.lisp > index 3bdebbcf1..15d9c9357 100644 > --- a/src/pcl/documentation.lisp > +++ b/src/pcl/documentation.lisp > @@ -216,7 +216,45 @@ > (t > x))) > (documentation (call-next-method))) > - (maybe-add-deprecation-note namespace name documentation))) > + (maybe-add-deprecation-note namespace name > + (normalize-sbcl-docstring x name doc-type > + documentation)))) > + > +(defvar *normalize-sbcl-docstrings* t) > + > +(defun normalize-sbcl-docstring (object name doc-type docstring) > + (if (and *normalize-sbcl-docstrings* > + docstring > + (sbcl-definition-name-p object name doc-type)) > + (string-right-trim '(#\Newline) (markdown-to-plain-text > + (reindent-docstring docstring))) > + docstring)) > + > +(defun non-setf-name (name) > + (if (and (consp name) > + (eq (first name) 'setf) > + (consp (cdr name))) > + (second name) > + name)) > + > +(defun sbcl-package-name-p (name) > + (when (stringp name) > + (or (string= name "COMMON-LISP") > + (and (>= (length name) 3) > + (string= name "SB-" :end1 3))))) > + > +(defun sbcl-definition-name-p (object name doc-type) > + (and (member doc-type '(t compiler-macro function method-combination setf > + structure type variable declaration)) > + (if (and (packagep object) (eq doc-type t)) > + (sbcl-package-name-p (package-name name)) > + (let ((name (non-setf-name name))) > + (if (null name) > + (null object) > + (when (symbolp name) > + (let ((package (symbol-package name))) > + (when package > + (sbcl-package-name-p (package-name package)))))))))) > > ;;; functions, macros, and special forms > > @@ -467,3 +505,210 @@ comparison.") > > (dolist (args (prog1 *!docstrings* (makunbound '*!docstrings*))) > (apply #'(setf documentation) args)) > + > +;;;; Markdown to plain text along the lines of SB-MANUAL::MARKDOWN-TO-TEXINFO > + > +(defun markdown-to-plain-text (string) > + (markdown-lines-to-plain-text (string-lines string) 0)) > + > +(defun markdown-lines-to-plain-text (lines base-indent) > + (with-output-to-string (out) > + (let ((buf nil)) > + (labels ((flush () > + (write-md-paragraph buf out) > + (setq buf nil))) > + (loop for l below (length lines) > + for line = (svref lines l) > + do (let ((n (write-md-block lines l base-indent out #'flush))) > + (if n > + (incf l (1- n)) > + (push line buf)))) > + (flush))))) > + > +(defun string-lines (string) > + (coerce (with-input-from-string (s string) > + (loop for line = (read-line s nil nil) > + while line collect line)) > + 'vector)) > + > +(defun whitespacep (char) > + (find char #(#\Tab #\Space #\Page #\Newline #\Return))) > + > +(defun indentation (line) > + (position-if-not #'whitespacep line)) > + > +(defun blankp (line) > + (null (indentation line))) > + > +(defun write-md-paragraph (reversed-lines out) > + (when reversed-lines > + (let* ((str (format nil "~{~A~^~%~}" (reverse reversed-lines))) > + (len (length str)) > + (bound t)) > + (loop for i below len > + for c = (char str i) > + do (cond > + ((and bound (char= c #\\)) > + (when (and (< (1+ i) len) > + (char= (char str (1+ i)) #\\)) > + (incf i)) > + (setf bound nil)) > + ((char= c #\`) > + (incf i) > + (loop repeat 2 > + while (< i len) > + while (char= (char str i) #\\) > + do (incf i)) > + (loop while (< i len) > + while (char/= (char str i) #\`) > + do (write-char (char str i) out) > + (incf i)) > + (setq bound nil)) > + (t > + (write-char c out) > + (setq bound (whitespacep c))))) > + (terpri out)))) > + > +(defun write-md-block (lines index base-indent out flush-fn) > + (or (write-md-fenced-code lines index base-indent out flush-fn) > + (write-md-indented-code lines index base-indent out flush-fn) > + (write-md-blockquote lines index base-indent out flush-fn) > + (write-md-markdown-itemize lines index base-indent out flush-fn))) > + > +(defun write-md-fenced-code (lines start base-indent out flush-fn) > + (declare (ignore base-indent)) > + (let* ((line (svref lines start)) > + (i (indentation line))) > + (when (and i (>= (length line) (+ i 3)) > + (string= line "```" :start1 i :end1 (+ i 3))) > + (funcall flush-fn) > + (loop for l from (1+ start) below (length lines) > + for line = (svref lines l) > + for ind = (indentation line) > + if (and ind (>= (length line) (+ ind 3)) > + (string= line "```" :start1 ind :end1 (+ ind 3))) > + do (return (- (1+ l) start)) > + else do (write-line line out) > + finally (return (- l start)))))) > + > +(defun write-md-indented-code (lines start base-indent out flush-fn) > + (when (or (zerop start) > + (blankp (svref lines (1- start)))) > + (let ((i (indentation (svref lines start)))) > + (when (and i (>= i (+ base-indent 4))) > + (funcall flush-fn) > + (loop for l from start below (length lines) > + for line = (svref lines l) > + for ind = (indentation line) > + while (or (null ind) > + (>= ind (+ base-indent 4))) > + do (write-line line out) > + finally (return (- l start))))))) > + > +(defun write-md-blockquote (lines start base-indent out flush-fn) > + (when (or (zerop start) > + (blankp (svref lines (1- start)))) > + (let ((i (indentation (svref lines start)))) > + (when (and i (<= i (+ base-indent 3)) > + (char= (char (svref lines start) i) #\>)) > + (funcall flush-fn) > + (let (stripped prefixes) > + (loop for l from start below (length lines) > + for line = (svref lines l) > + for ind = (indentation line) > + while (and ind (<= ind (+ base-indent 3)) > + (char= (char line ind) #\>)) > + do (let ((c-start (if (and (< (1+ ind) (length line)) > + (char= (char line (1+ ind)) #\Space)) > + (+ ind 2) (1+ ind)))) > + (push (subseq line c-start) stripped) > + (push (subseq line 0 c-start) prefixes))) > + (let ((inner (markdown-lines-to-plain-text > + (coerce (nreverse stripped) 'vector) 0))) > + (loop for prefix in (nreverse prefixes) > + for line across (string-lines inner) > + do (write-string prefix out) > + (write-line line out))) > + (length prefixes)))))) > + > +(defun maybe-itemize-offset (line) > + (let ((i (indentation line))) > + (when (and i (< (1+ i) (length line)) > + (find (char line i) "-*") > + (char= (char line (1+ i)) #\Space)) > + i))) > + > +(defun write-md-markdown-itemize (lines start base-indent out flush-fn) > + (when (eql (maybe-itemize-offset (svref lines start)) base-indent) > + (funcall flush-fn) > + (let ((child (+ base-indent 4)) > + (buf nil)) > + (labels ((flush () > + (write-md-paragraph buf out) > + (setq buf nil))) > + (loop for l from start below (length lines) > + for line = (svref lines l) > + for ind = (indentation line) > + do (cond ((blankp line) > + (flush) > + (write-line line out)) > + ((eql (maybe-itemize-offset line) base-indent) > + (flush) > + (push line buf)) > + ((>= ind child) > + (let ((n (write-md-block lines l child out #'flush))) > + (if n > + (incf l (1- n)) > + (push line buf)))) > + ((> ind base-indent) > + (push line buf)) > + (t > + (loop-finish))) > + finally (flush) > + (return (- l start))))))) > + > + > +;;; Normalize docstring indentation by stripping the longest run of > +;;; leading spaces common to all non-blank lines except the first. > +(defun reindent-docstring (docstring) > + (let ((indentation (docstring-indentation docstring))) > + (strip-docstring-indent docstring indentation t))) > + > +;;; Return the minimum number of leading spaces in non-blank lines > +;;; after the first. > +(defun docstring-indentation (docstring &key (first-line-special-p t)) > + (let ((n-min-indentation nil)) > + (with-input-from-string (s docstring) > + (loop for i upfrom 0 > + for line = (read-line s nil nil) > + while line > + do (when (and (or (not first-line-special-p) (plusp i)) > + (not (blankp line))) > + (when (or (null n-min-indentation) > + (< (n-leading-spaces line) n-min-indentation)) > + (setq n-min-indentation (n-leading-spaces line)))))) > + (or n-min-indentation 0))) > + > +(defun n-leading-spaces (line) > + (let ((n 0)) > + (loop for i below (length line) > + while (char= (aref line i) #\Space) > + do (incf n)) > + n)) > + > +(defun strip-docstring-indent (docstring indentation first-line-special-p) > + (with-output-to-string (out) > + (with-input-from-string (s docstring) > + (loop for i upfrom 0 > + do (multiple-value-bind (line missing-newline-p) > + (read-line s nil nil) > + (unless line > + (return)) > + (write-string (if (and first-line-special-p > + (zerop i)) > + line > + (subseq line (min (length line) > + indentation))) > + out) > + (unless missing-newline-p > + (terpri out))))))) > > ----------------------------------------------------------------------- > > > hooks/post-receive > -- > SBCL > > > _______________________________________________ > Sbcl-commits mailing list > [email protected] > https://lists.sourceforge.net/lists/listinfo/sbcl-commits _______________________________________________ Sbcl-commits mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/sbcl-commits