Subject: [PATCH] Copy descriptions from manual into docstrings

From: Morgan Smith <[email-masked]>
Date: Tue, 12 Dec 2023 13:12:09 -0500

* module/ice-9/binary-ports.scm (call-with-output-bytevector, seek,
make-custom-binary-input-port, make-custom-binary-output-port,
make-custom-binary-input/output-port):
* module/ice-9/ports.scm (open-input-file, open-output-file,
with-input-from-port, with-output-to-port, with-error-to-port):
* module/ice-9/suspendable-ports.scm (install-suspendable-ports!,
uninstall-suspendable-ports!):
* module/ice-9/textual-ports.scm (get-char, lookahead-char, unget-char,
unget-string, get-line, get-string-all, get-string-n,
make-custom-textual-input/output-port):
* module/rnrs/io/ports.scm (buffer-mode?, native-eol-style,
make-transcoder, native-transcoder, display, get-char, get-datum,
get-line, get-string-all):

Copy descriptions from manual into docstrings
---

Hello!

Currently we have many functions that are only documented in the manual.  As in
they have no docstring.  Ideally, we would autogenerate the manual (or at least
parts of it) from docstrings (do I have to defend this point?).

I would like to work towards this end.  I would appreciate help, but I believe
this is a task I can accomplish.

Attached is a patch that copies many docstrings from api-io.texi to their
functions.  api-io.texi is the first texi file I will try to auto-generate.
This file was selected arbitrarily.

My current plan is the use the module "(texinfo reflection)" to try and
generate the texi files.

The work is progressing slower then I'd like as the guile codebase is massive
(it took me a long time to discover "(texinfo reflection)".  I also don't have
a lot of experience with scheme or guile.

The main reason I'm submitting this patch is to let others know I'm working on
this.

 module/ice-9/binary-ports.scm      | 100 ++++++++++++++++++++++++++++-
 module/ice-9/ports.scm             |  32 +++++++--
 module/ice-9/suspendable-ports.scm |   5 ++
 module/ice-9/textual-ports.scm     |  89 +++++++++++++++++++++----
 module/rnrs/io/ports.scm           |  85 ++++++++++++++++++++----
 5 files changed, 279 insertions(+), 32 deletions(-)

diff --git a/module/ice-9/binary-ports.scm b/module/ice-9/binary-ports.scm
index b7eddc93d..5a2296df8 100644
--- a/module/ice-9/binary-ports.scm
+++ b/module/ice-9/binary-ports.scm
@@ -63,8 +63,9 @@ read.  All values yielded by @var{proc} are returned."
 
 (define (call-with-output-bytevector proc)
   "Call the one-argument procedure @var{proc} with a newly created
-binary output port.  When the function returns, port is closed and the
-bytevector composed of the bytes written into the port is returned."
+bytevector output port.  When the function returns, the bytevector
+composed of the characters written into the port is returned.
+@var{proc} should not close the port."
   (call-with-values
       (lambda ()
         (open-bytevector-output-port))
@@ -107,6 +108,33 @@ bytevector composed of the bytes written into the port is returned."
       (type-error "custom-binary-port-seek" "procedure" set-position!)))
 
   (define (seek port offset whence)
+    "Sets the current position of @var{fd_port} to the integer
+@var{offset}.  For a file port, @var{offset} is expressed
+as a number of bytes; for other types of ports, such as string
+ports, @var{offset} is an abstract representation of the
+position within the port's data, not necessarily expressed
+as a number of bytes.  @var{offset} is interpreted according to
+the value of @var{whence}.
+
+One of the following variables should be supplied for
+@var{whence}:
+@defvar SEEK_SET
+Seek from the beginning of the file.
+@end defvar
+@defvar SEEK_CUR
+Seek from the current position.
+@end defvar
+@defvar SEEK_END
+Seek from the end of the file.
+@end defvar
+If @var{fd_port} is a file descriptor, the underlying system
+call is @code{lseek}.  @var{port} may be a string port.
+
+The value returned is the new position in @var{fd_port}.  This means
+that the current position of a port can be obtained using:
+@lisp
+(seek port 0 SEEK_CUR)
+@end lisp"
     (cond
      ((eqv? whence SEEK_CUR)
       (unless get-position
@@ -140,6 +168,56 @@ bytevector composed of the bytes written into the port is returned."
       (lambda (port) #f)))
 
 (define (make-custom-binary-input-port id read get-position set-position! close)
+  "Return a new custom binary input port named @var{id} (a string) whose
+input is drained by invoking @var{read!} and passing it a bytevector, an
+index where bytes should be written, and the number of bytes to read.
+The @code{read!}  procedure must return an integer indicating the number
+of bytes read, or @code{0} to indicate the end-of-file.
+
+Optionally, if @var{get-position} is not @code{#f}, it must be a thunk
+that will be called when @code{port-position} is invoked on the custom
+binary port and should return an integer indicating the position within
+the underlying data stream; if @var{get-position} was not supplied, the
+returned port does not support @code{port-position}.
+
+Likewise, if @var{set-position!} is not @code{#f}, it should be a
+one-argument procedure.  When @code{set-port-position!} is invoked on the
+custom binary input port, @var{set-position!} is passed an integer
+indicating the position of the next byte is to read.
+
+Finally, if @var{close} is not @code{#f}, it must be a thunk.  It is
+invoked when the custom binary input port is closed.
+
+The returned port is fully buffered by default, but its buffering mode
+can be changed using @code{setvbuf} (@pxref{Buffering}).
+
+Using a custom binary input port, the @code{open-bytevector-input-port}
+procedure (@pxref{Bytevector Ports}) could be implemented as follows:
+
+@lisp
+(define (open-bytevector-input-port source)
+  (define position 0)
+  (define length (bytevector-length source))
+
+  (define (read! bv start count)
+    (let ((count (min count (- length position))))
+      (bytevector-copy! source position
+                        bv start count)
+      (set! position (+ position count))
+      count))
+
+  (define (get-position) position)
+
+  (define (set-position! new-position)
+    (set! position new-position))
+
+  (make-custom-binary-input-port \"the port\" read!
+                                  get-position set-position!
+                                  #f))
+
+(read (open-bytevector-input-port (string->utf8 \"hello\")))
+@result{} hello
+@end lisp"
   (unless (string? id)
     (type-error "make-custom-binary-input-port" "string" id))
   (make-custom-port #:id id
@@ -154,6 +232,15 @@ bytevector composed of the bytes written into the port is returned."
 
 (define (make-custom-binary-output-port id write get-position set-position!
                                         close)
+  "Return a new custom binary output port named @var{id} (a string) whose
+output is sunk by invoking @var{write!} and passing it a bytevector, an
+index where bytes should be read from this bytevector, and the number of
+bytes to be ``written''.  The @code{write!}  procedure must return an
+integer indicating the number of bytes actually written; when it is
+passed @code{0} as the number of bytes to write, it should behave as
+though an end-of-file was sent to the byte sink.
+
+The other arguments are as for @code{make-custom-binary-input-port}."
   (unless (string? id)
     (type-error "make-custom-binary-output-port" "string" id))
   (make-custom-port #:id id
@@ -168,6 +255,15 @@ bytevector composed of the bytes written into the port is returned."
 
 (define (make-custom-binary-input/output-port id read write get-position
                                               set-position! close)
+  "Return a new custom binary input/output port named @var{id} (a string).
+The various arguments are the same as for The other arguments are as for
+@code{make-custom-binary-input-port} and
+@code{make-custom-binary-output-port}.  If buffering is enabled on the
+port, as is the case by default, input will be buffered in both
+directions; @xref{Buffering}.  If the @var{set-position!} function is
+provided and not @code{#f}, then the port will also be marked as
+random-access, causing the buffer to be flushed between reads and
+writes."
   (unless (string? id)
     (type-error "make-custom-binary-input/output-port" "string" id))
   (make-custom-port #:id id
diff --git a/module/ice-9/ports.scm b/module/ice-9/ports.scm
index bb05769a3..f834b416b 100644
--- a/module/ice-9/ports.scm
+++ b/module/ice-9/ports.scm
@@ -407,18 +407,30 @@ interpret its input and output."
 
 (define* (open-input-file
           file #:key (binary #f) (encoding #f) (guess-encoding #f))
-  "Takes a string naming an existing file and returns an input port
-capable of delivering characters from the file.  If the file
-cannot be opened, an error is signaled."
+  "Open @var{filename} for input.  If @var{binary} is true, open the port
+in binary mode, otherwise use text mode.  @var{encoding} and
+@var{guess-encoding} determine the character encoding as described above
+for @code{open-file}.  Equivalent to
+@lisp
+(open-file @var{filename}
+           (if @var{binary} \"rb\" \"r\")
+           #:guess-encoding @var{guess-encoding}
+           #:encoding @var{encoding})
+@end lisp"
   (open-file file (if binary "rb" "r")
              #:encoding encoding
              #:guess-encoding guess-encoding))
 
 (define* (open-output-file file #:key (binary #f) (encoding #f))
-  "Takes a string naming an output file to be created and returns an
-output port capable of writing characters to a new file by that
-name.  If the file cannot be opened, an error is signaled.  If a
-file with the given name already exists, the effect is unspecified."
+  "Open @var{filename} for output.  If @var{binary} is true, open the port
+in binary mode, otherwise use text mode.  @var{encoding} specifies the
+character encoding as described above for @code{open-file}.  Equivalent
+to
+@lisp
+(open-file @var{filename}
+           (if @var{binary} \"wb\" \"w\")
+           #:encoding @var{encoding})
+@end lisp"
   (open-file file (if binary "wb" "w")
              #:encoding encoding))
 
@@ -468,14 +480,20 @@ never again be used for a read or write operation."
     (call-with-port p proc)))
 
 (define (with-input-from-port port thunk)
+  "Call @var{thunk} in a dynamic environment in which
+@code{current-input-port} is rebound to the given @var{port}."
   (parameterize ((current-input-port port))
     (thunk)))
 
 (define (with-output-to-port port thunk)
+  "Call @var{thunk} in a dynamic environment in which
+@code{current-output-port} is rebound to the given @var{port}."
   (parameterize ((current-output-port port))
     (thunk)))
 
 (define (with-error-to-port port thunk)
+  "Call @var{thunk} in a dynamic environment in which
+@code{current-error-port} is rebound to the given @var{port}."
   (parameterize ((current-error-port port))
     (thunk)))
 
diff --git a/module/ice-9/suspendable-ports.scm b/module/ice-9/suspendable-ports.scm
index 9fac1df62..ab1c84ff5 100644
--- a/module/ice-9/suspendable-ports.scm
+++ b/module/ice-9/suspendable-ports.scm
@@ -759,6 +759,9 @@
      put-char put-string)
     ((ice-9 rdelim) %read-line read-line read-delimited)))
 (define (install-suspendable-ports!)
+  "Replace the core ports implementation with suspendable ports, as
+described above.  This will mutate the values of the bindings like
+@code{get-char}, @code{put-u8}, and so on in place."
   (unless saved-port-bindings
     (set! saved-port-bindings (make-hash-table))
     (let ((suspendable-ports (resolve-module '(ice-9 suspendable-ports))))
@@ -775,6 +778,8 @@
        port-bindings))))
 
 (define (uninstall-suspendable-ports!)
+  "Restore the original core ports implementation, un-doing the effect of
+@code{install-suspendable-ports!}."
   (when saved-port-bindings
     (for-each
      (match-lambda
diff --git a/module/ice-9/textual-ports.scm b/module/ice-9/textual-ports.scm
index ac551be7a..34b30ea17 100644
--- a/module/ice-9/textual-ports.scm
+++ b/module/ice-9/textual-ports.scm
@@ -42,34 +42,76 @@
             make-custom-textual-output-port
             make-custom-textual-input/output-port))
 
-(define (get-char port)
-  (read-char port))
+(define (get-char input-port)
+  "Reads from @var{input-port}, blocking as necessary, until a
+complete character is available from @var{input-port},
+or until an end of file is reached.
 
-(define (lookahead-char port)
-  (peek-char port))
+If a complete character is available before the next end of file,
+@code{get-char} returns that character and updates the input port to
+point past the character. If an end of file is reached before any
+character is read, @code{get-char} returns the end-of-file object."
+  (read-char input-port))
+
+(define (lookahead-char input-port)
+  "The @code{lookahead-char} procedure is like @code{get-char}, but it does
+not update @var{input-port} to point past the character."
+  (peek-char input-port))
 
 (define (unget-char port char)
+  "Place character @var{char} in @var{port} so that it will be read by the
+next read operation.  If called multiple times, the unread characters
+will be read again in last-in first-out order."
   (unread-char char port))
 
 (define* (unget-string port string #:optional (start 0)
                        (count (- (string-length string) start)))
+  "Place the string @var{str} in @var{port} so that its characters will
+be read from left-to-right as the next characters from @var{port}
+during subsequent read operations.  If called multiple times, the
+unread characters will be read again in last-in first-out order."
   (unread-string (if (and (zero? start)
                           (= count (string-length string)))
                      string
                      (substring/shared string start (+ start count)))
                  port))
 
-(define (get-line port)
-  (read-line port 'trim))
+(define (get-line input-port)
+  "Reads from @var{input-port} up to and including the linefeed
+character or end of file, decoding characters in the same manner as
+@code{get-string-n} and @code{get-string-n!}.
+
+If a linefeed character is read, a string containing all of the text up
+to (but not including) the linefeed character is returned, and the port
+is updated to point just past the linefeed character. If an end of file
+is encountered before any linefeed character is read, but some
+characters have been read and decoded as characters, a string containing
+those characters is returned. If an end of file is encountered before
+any characters are read, the end-of-file object is returned."
+  (read-line input-port 'trim))
 
-(define (get-string-all port)
-  (read-string port))
+(define (get-string-all input-port)
+  "Reads from @var{input-port} until an end of file, decoding characters in
+the same manner as @code{get-string-n} and @code{get-string-n!}.
+
+If characters are available before the end of file, a string containing
+all the characters decoded from that data are returned. If no character
+precedes the end of file, the end-of-file object is returned."
+  (read-string input-port))
 
 (define (get-string-n port count)
-  "Read up to @var{count} characters from @var{port}.
-If no characters could be read before encountering the end of file,
-return the end-of-file object, otherwise return a string containing
-the characters read."
+  "The @code{get-string-n} procedure reads from @var{input-port}, blocking
+as necessary, until @var{count} characters are available, or until an
+end of file is reached.  @var{count} must be an exact, non-negative
+integer, representing the number of characters to be read.
+
+If @var{count} characters are available before end of file,
+@code{get-string-n} returns a string consisting of those @var{count}
+characters. If fewer characters are available before an end of file, but
+one or more characters can be read, @code{get-string-n} returns a string
+containing those characters. In either case, the input port is updated
+to point just past the characters read. If no characters can be read
+before an end of file, the end-of-file object is returned."
   (let* ((s (make-string count))
          (rv (get-string-n! port s 0 count)))
     (cond ((eof-object? rv) rv)
@@ -210,6 +252,29 @@ the characters read."
 
 (define (make-custom-textual-input/output-port id read write get-position
                                                set-position! close)
+  "Like their custom binary port counterparts, but for textual ports.
+Concretely this means that instead of being passed a bytevector, the
+@var{read} function is passed a mutable string to fill, and likewise for
+the buffer supplied to @var{write}.  Port positions are still expressed
+in bytes, however.
+
+If string ports were not supplied with Guile, we could implement them
+With custom textual ports:
+@example
+(define (open-string-input-port source)
+  (define position 0)
+  (define length (string-length source))
+
+  (define (read! dst start count)
+    (let ((count (min count (- length position))))
+      (string-copy! dst start source position (+ position count))
+      (set! position (+ position count))
+      count))
+
+  (make-custom-textual-input-port \"strport\" read! #f #f #f))
+
+(read (open-string-input-port \"hello\"))
+@end example"
   (unless (string? id)
     (type-error "make-custom-textual-input/output-port" "string" id))
   (define-values (%read %flush-input)
diff --git a/module/rnrs/io/ports.scm b/module/rnrs/io/ports.scm
index d7cb89e36..a72ca6b4f 100644
--- a/module/rnrs/io/ports.scm
+++ b/module/rnrs/io/ports.scm
@@ -147,6 +147,8 @@
   buffer-modes)
 
 (define (buffer-mode? symbol)
+  "Returns @code{#t} if the argument is a valid buffer-mode symbol, and
+returns @code{#f} otherwise."
   (enum-set-member? symbol (enum-set-universe (buffer-modes))))
 
 (define-enumeration eol-style
@@ -154,6 +156,8 @@
   eol-styles)
 
 (define (native-eol-style)
+  "Returns the default end-of-line style of the underlying platform, e.g.,
+@code{lf} on Unix and @code{crlf} on Windows."
   (eol-style none))
 
 (define-enumeration error-handling-mode
@@ -167,9 +171,19 @@
                           #:optional
                           (eol-style (native-eol-style))
                           (handling-mode (error-handling-mode replace)))
+  "@var{codec} must be a codec; @var{eol-style}, if present, an eol-style
+symbol; and @var{handling-mode}, if present, an error-handling-mode
+symbol.
+
+@var{eol-style} may be omitted, in which case it defaults to the native
+end-of-line style of the underlying platform.  @var{handling-mode} may
+be omitted, in which case it defaults to @code{replace}.  The result is
+a transcoder with the behavior specified by its arguments."
   (%make-transcoder codec eol-style handling-mode))
 
 (define (native-transcoder)
+  "Returns an implementation-dependent transcoder that represents a
+possibly locale-dependent ``native'' transcoding."
   (make-transcoder (or (fluid-ref %default-port-encoding)
                        (latin-1-codec))))
 
@@ -469,6 +483,13 @@ line buffered, or @code{block} otherwise."
 ;; Defined here to be able to make use of `with-i/o-encoding-error', but
 ;; not exported from here, but from `(rnrs io simple)'.
 (define* (display object #:optional (port (current-output-port)))
+  "Send a representation of @var{obj} to @var{port} or to the current
+output port if not given.
+
+The output is designed for human readability, it differs from
+@code{write} in that strings are printed without double quotes and
+escapes, and characters are printed as per @code{write-char}, not in
+@samp{#\\} form."
   (with-textual-output-conditions port (guile:display object port)))
 
 
@@ -491,17 +512,59 @@ line buffered, or @code{block} otherwise."
        (lambda (key subr message errno port)
          (raise (make-i/o-decoding-error port)))))))
 
-(define (get-char port)
-  (with-textual-input-conditions port (read-char port)))
-
-(define (get-datum port)
-  (with-textual-input-conditions port (read port)))
-
-(define (get-line port)
-  (with-textual-input-conditions port (read-line port 'trim)))
-
-(define (get-string-all port)
-  (with-textual-input-conditions port (read-string port)))
+(define (get-char input-port)
+  "Reads from @var{input-port}, blocking as necessary, until a
+complete character is available from @var{input-port},
+or until an end of file is reached.
+
+If a complete character is available before the next end of file,
+@code{get-char} returns that character and updates the input port to
+point past the character. If an end of file is reached before any
+character is read, @code{get-char} returns the end-of-file object."
+  (with-textual-input-conditions input-port (read-char input-port)))
+
+(define (get-datum input-port)
+  "Reads an external representation from @var{textual-input-port} and returns
+the datum it represents.  The @code{get-datum} procedure returns the next
+datum that can be parsed from the given @var{textual-input-port}, updating
+@var{textual-input-port} to point exactly past the end of the external
+representation of the object.
+
+Any @emph{interlexeme space} (comment or whitespace, @pxref{Scheme
+Syntax}) in the input is first skipped.  If an end of file occurs after
+the interlexeme space, the end-of-file object is returned.
+
+If a character inconsistent with an external representation is
+encountered in the input, an exception with condition types
+@code{&lexical} and @code{&i/o-read} is raised.  Also, if the end of
+file is encountered after the beginning of an external representation,
+but the external representation is incomplete and therefore cannot be
+parsed, an exception with condition types @code{&lexical} and
+@code{&i/o-read} is raised."
+  (with-textual-input-conditions input-port (read input-port)))
+
+(define (get-line input-port)
+  "Reads from @var{input-port} up to and including the linefeed
+character or end of file, decoding characters in the same manner as
+@code{get-string-n} and @code{get-string-n!}.
+
+If a linefeed character is read, a string containing all of the text up
+to (but not including) the linefeed character is returned, and the port
+is updated to point just past the linefeed character. If an end of file
+is encountered before any linefeed character is read, but some
+characters have been read and decoded as characters, a string containing
+those characters is returned. If an end of file is encountered before
+any characters are read, the end-of-file object is returned."
+  (with-textual-input-conditions input-port (read-line input-port 'trim)))
+
+(define (get-string-all input-port)
+  "Reads from @var{input-port} until an end of file, decoding characters in
+the same manner as @code{get-string-n} and @code{get-string-n!}.
+
+If characters are available before the end of file, a string containing
+all the characters decoded from that data are returned. If no character
+precedes the end of file, the end-of-file object is returned."
+  (with-textual-input-conditions input-port (read-string input-port)))
 
 (define (get-string-n port count)
   "Read up to @var{count} characters from @var{port}.
-- 
2.41.0