9. 输入/输出操作

本章介绍了 Chez Scheme 的通用端口工具,端口操作,以及 Chez Scheme 对标准输入/输出操作集的多种扩展。有关标准输入/输出操作的说明,可参见《Scheme 编程语言》第 4 版的第 7 章,或《Scheme 语言修订6报告》。9.17 节给出了一些通用端口样例的定义。

在程序不再可以访问文件端口后,或在 Scheme 程序退出时,Chez Scheme 会自动关闭文件端口,但最好尽可能显式地关闭端口。

9.1. 通用端口

Chez Scheme 的“通用端口”工具支持程序员添加具有任意输入/输出语义的新文本端口类型。例如,它可以用于定义任何内置的 Common Lisp [30]流类型,即,异名流,广播流,级联流,双向流,回显流和字符串流。它还可用于定义更多独特的端口,例如表示位映射显示器上的窗口的端口,或表示通过管道或套接字连接到当前进程的进程的端口。

每个端口都有一个关联的端口处理程序。端口处理程序是一种接受面向对象风格的消息的过程。每条消息对应端口上的一个低级 Scheme 操作,例如,read-char 和 close-input-port(但 read 不是,它是基于低级别的操作定义的)。 大多数此类操作只是立即以相应的消息调用处理程序。

标准消息遵循以下约定:消息名称是处理程序的第一个参数。 它总是一个符号,且总是一个原生 Scheme 端口操作的名称。 附加参数与原生过程的参数相同,并以相同的顺序出现。(某些原生过程的 port 参数是可选的;对于传递给处理程序的消息,始终提供 port 参数。)为内置端口定义了以下消息:

block-read port string count
block-write port string count
char-ready? port
clear-input-port port
clear-output-port port
close-port port
file-position port
file-position port position
file-length port
flush-output-port port
peek-char port
port-name port
read-char port
unread-char char port
write-char char port

用户定义的端口可以接受其它消息。

Chez Scheme 的输入和输出通常会被缓冲以提高效率。为了支持缓冲,每个输入端口包含一个输入缓冲区,每个输出端口包含一个输出缓冲区。双向端口,既是输入端口又是输出端口的端口,包含输入和输出缓冲区。如果输入缓冲区是空字符串,则不缓冲输入,如果输出缓冲区是空字符串,则不缓冲输出。在无缓冲的输入和输出的情况下,对 read-char,write-char 和类似消息的调用,会导致立即以相应的消息调用处理程序。对于缓冲的输入和输出,对这些过程的调用会导致缓冲区被更新,并且在正常情况下不会调用处理程序,直到缓冲区变空(对于输入)或变满(对于输出)。但是,当接收到 read-char,write-char 和类似消息时,由于有以下可能性:(a)处理程序可能通过某种其他机制调用,或(b)对处理程序的调用被中断,因而缓冲端口的处理程序一定不能依赖于缓冲区为空或为满。

在存在键盘,定时器和其他中断的情况下,可能会中断对端口处理程序的调用或者中断处理程序本身。如果端口可以在被中断的代码之外访问,则中断处理程序可能会导致在端口上执行输入或输出。如上所述,这是一个原因,当接收到 read-char,write-char 或类似消息时,端口处理程序一定不能依赖于输入缓冲区为空或输出缓冲区已满。此外,端口处理程序可能只在禁用中断的情况下需要操作缓冲区(使用 with-interrupts-disabled)。

通用端口是通过本章后面定义的端口构造过程 make-input-port,make-output-port 和 make-input/output-port 之一创建的。端口有七个可访问字段:

handler, 通过 port-handler 访问; output-buffer, 通过 port-output-buffer 访问, output-size, 通过 port-output-size 访问, output-index, 通过 port-output-index 访问, input-buffer, 通过 port-input-buffer 访问, input-size, 通过 port-input-size 访问, 以及 input-index, 通过 port-input-index 访问.

output-size 和 output-index 字段仅对输出端口有效,input-size 和 input-index 字段仅对输入端口有效。输出和输入的大小和索引字段也可以使用相应的 "set-field!" 过程进行更新。

端口的输出大小决定了实际可用于 write-char 写入的端口输出缓冲区的大小。输出大小通常与端口输出缓冲区的字符串长度相同,但可以根据编程人员的判断将其设置得更小(但不小于零)。输出索引决定将在端口缓冲区中的哪个位置写入下一个字符。输出索引应介于 0 和输出大小之间(两端包含)。如果自上次刷新缓冲区以来未发生任何输出,则输出索引应为 0. 如果索引小于输出大小,则 write-char 将其字符参数存储到缓冲区中的指定字符位置并递增索引。如果索引等于输出大小,则 write-char 保持端口的字段不变并调用处理程序。

端口的输入大小决定了实际可用于 read-char 读取的端口输入缓冲区的大小。端口的输入大小和输入索引的约束方式与输出大小和输出索引相同,即,输入大小必须介于 0 和输入缓冲区的字符串长度(两端包含)之间,输入索引必须介于 0 和输入大小之间(两端包含)。通常,由于可读取的字符数少于缓冲区的容量,输入大小小于输入缓冲区的长度。输入索引决定将从输入缓冲区中的哪个位置读取下一个字符。如果索引小于输入大小,则 read-char 将提取此位置中的字符,递增索引并返回该字符。如果索引等于输入大小,则 read-char 保持端口的字段不变并调用处理程序。

peek-char 的操作类似于 read-char,除了它不增加输入索引。如果输入索引大于 0,则 unread-char 会递减输入索引,否则它会调用处理程序。如果输入索引小于输入大小,则 char-ready? 返回#t,否则它调用处理程序。

尽管上面显示和讨论的字段在逻辑上存在于端口中,但实际的实现细节可能不同。当前的 Chez Scheme 实现使用不同的表示,允许以最小的开销对 read-char,write-char 及类似操作进行开放编码。访问和赋值运算符执行实际表示形式与上面显示的表示形式之间的转换。

接收消息的端口处理程序必须返回适用于相应操作的值。例如,接收 read-char 消息的处理程序必须返回一个字符或 eof 对象(如果它返回)。对于返回未指定值的操作(例如 close-port),处理程序不需要返回任何特定值。

9.2. 文件选项

《Scheme 语言修订6报告》要求文件选项枚举集合的全集必须包含 no-create,no-fail 和 no-truncate,其含义在《Scheme 编程语言》第 4 版 7.2 节中关于文件选项语法的介绍中有所描述。Chez Scheme 定义了许多其他文件选项:

compressed: 写入时应压缩输出文件; 读取时应解压缩压缩的输入文件。

replace: 仅用于输出文件,替换(删除并重新创建)现有文件(如果存在)。

exclusive: 仅用于输出文件,锁定文件以进行独占访问。在某些系统上,锁定是建议性的,即,只有当其它进程也试图独占打开文件时,它才会禁止它们访问。

append: 仅用于输出文件,在每次写入之前将输出端口置于文件末尾,从而始终将发送到端口的输出附加到文件末尾。

perm-set-user-id: 仅用于基于 Unix 的系统下新创建的输出文件,设置 user-id 位。

perm-set-group-id: 仅用于基于 Unix 的系统下新创建的输出文件,设置 group-id 位。

perm-sticky: 仅用于基于 Unix 的系统下新创建的输出文件,设置 sticky 位。

perm-no-user-read: 仅用于基于 Unix 的系统下新创建的输出文件,不设置用户读取位。(用户读取位默认是设置的,除非被进程 umask 屏蔽。)

perm-no-user-write: 仅用于基于 Unix 的系统下新创建的输出文件,不设置用户写入位。(用户写入位默认是设置的,除非被进程 umask 屏蔽。)

perm-user-execute: 仅用于基于 Unix 的系统下新创建的输出文件,除非被进程 umask 屏蔽,否则设置用户执行位。(用户执行位默认是不设置的。)

perm-no-group-read: 仅用于基于 Unix 的系统下新创建的输出文件,不设置组读取位。(组读取位默认是设置的,除非被进程 umask 屏蔽。)

perm-no-group-write: 仅用于基于 Unix 的系统下新创建的输出文件,不设置组写入位。(组写入位默认是设置的,除非被进程 umask 屏蔽。)

perm-group-execute: 仅用于基于 Unix 的系统下新创建的输出文件,除非被进程 umask 屏蔽,否则设置组执行位。(组执行位默认是不设置的。)

perm-no-other-read: 仅用于基于 Unix 的系统下新创建的输出文件,不设置其他用户读取位。(其他用户读取位默认是设置的,除非被进程 umask 屏蔽。)

perm-no-other-write: 仅用于基于 Unix 的系统下新创建的输出文件,不设置其他用户写入位。(其他用户写入位默认是设置的,除非被进程 umask 屏蔽。)

perm-other-execute: 仅用于基于 Unix 的系统下新创建的输出文件,除非被进程 umask 屏蔽,否则设置其他用户执行位。(其他用户执行位默认是不设置的。)

9.3. 编码转换器

《Scheme 语言修订6报告》提供了三种内置编解码器:latin-1 编解码器,utf-8 编解码器和 utf-16 编解码器。Chez Scheme 提供了三个额外的编解码器:utf-16le 编解码器,utf-16be 编解码器和用于非 Unicode 字符集的“iconv”编解码器。它还提供了标准 utf-16 编解码器的替代方案,默认为小端序格式而不是默认的大端序格式。本节描述了这些编解码器,以及一个 current-transcoder 参数,它允许程序员在编码转换器为隐式时决定用于文本端口的编码转换器,如 open-input-file 或 load,以及谓词 transcoder?, 标准中应该包含,但却没有。

procedure: (utf-16-codec) procedure: (utf-16-codec endianness) procedure: (utf-16le-codec) procedure: (utf-16be-codec) returns: 一个编解码器 libraries: (chezscheme)

endianness 必须是符号 big 或符号 little.

utf-16-codec 返回的编解码器可用于创建和处理 UTF-16 格式的数据。当调用时不传入参数 endianness 或参数 endianness 为 big 时,utf-16-codec 返回标准 UTF-16 数据的编解码器,即,如果没有找到字节顺序标记(BOM),则默认为大端序格式的编解码器。

当使用基于此编解码器的编码转换器对输出进行编码转换时,会在写入第一个字符之前发出 BOM,并且每个字符都以大端序格式写为 UTF-16 字符。 对于输入,在输入的开始处查找 BOM,如果存在,则控制其余 UTF-16 字符的字节序。如果没有 BOM,则假定为大端序。对于输入-输出端口,如果在写入之前读取文件,则不会发出 BOM,如果在读取之前写入文件,则不会查找 BOM。

对于通过 transcoded-port 创建的文本端口,仅当传递给 transcoded-port 的二进制端口位于数据流或文件的开头时,通过编码转换器写入或读取的 BOM 才会出现在底层数据流或文件的开头。当编码转换器可以确定是这种情况时,如果尝试将端口重新定位到数据流或文件的开头,它会设置一个标志,该标志会导致 set-port-position! 将端口定位在 BOM 之后,从而使 BOM 得以保留。

调用时参数 endianness 为 little 时,utf-16-codec 返回一个默认为小端序格式的编解码器,用于读取和写入。对于在读取之前写入的纯输出流或输入/输出流,结果为标准的 UTF-16,其中 BOM 指定小端序格式,后面跟着小端字节序的字符。对于在写入之前读取的纯输入流或输入/输出流,此编解码器允许程序从输入流中读取,这些输入流以 BOM 开头或以 UTF-16LE 格式编码。这对于处理可能由声称生成 UTF-16 文件但实际生成 UTF-16LE 文件的旧 Windows 应用程序生成的文件格外有用。

《Scheme 语言修订6报告》版本的 utf-16-codec 缺少可选的 endianness 参数。

utf-16le-codec 和 utf-16be-codec 返回的编解码器用于读取和写入 UTF-16LE 和 UTF-16BE 格式的数据,即,采用小端或大端字节序,且不带有 BOM 的 UTF-16. 对于输出,由于 BOM 不会隐式添加,并且 BOM 可以作为普通字符显式添加,因而这些编解码器对于控制是否以及在何处添加 BOM 十分有用。对于输入,这些编解码器对于处理已知为小端序或大端序格式而没有 BOM 的文件非常有用。

procedure: (iconv-codec code-page) returns: 一个编解码器 libraries: (chezscheme)

code-page 必须是字符串,并且应该标识目标机器上安装的 iconv 库接受的编解码器。此过程返回的编解码器可用于转换 iconv 支持的非 Unicode 单字节和多字节字符集。当在输入方向上使用时,编解码器将字节序列转换为 Scheme 字符串,并且当在输出方向上使用时,它将 Scheme 字符串转换为字节序列。

支持的代码页集取决于 iconv 的可用版本; 请参阅 iconv 文档或使用 shell 命令 iconv –list 获取支持的代码页列表。

虽然 Windows 操作系统不提供 iconv 库,但可以通过提供 iconv 动态链接库(名为 iconv.dll,libiconv.dll 或 libiconv-2.dll)在 Windows 系统上使用 iconv-codec. 这些动态链接库提供了符合 Posix 标准的 iconvopen,iconv 和 iconvclose 入口,可能就使用这些名称,或使用替代名称 libiconvopen,libiconv 和 libiconvclose。dll 必须位于 dll 的标准位置,或者在进程第一次调用 iconv-codec 时的当前目录中。

thread parameter: current-transcoder libraries: (chezscheme)

每当使用隐式编码转换器打开文本文件时,例如,通过 open-input-file 和其他便捷的 I/O 过程,compile-file include, load, 和 pretty-file, 就使用参数对象 current-transcoder 的编码转换器值。它的初始值是 native-transcoder 过程的值。

procedure: (transcoder? obj) 返回: 如果 obj 是编码转换器,则为 #t, 否则为 #f. libraries: (chezscheme)

9.4. 端口操作

本节描述了用于直接创建,访问和更改端口的过程。还描述了端口上的一些非标准操作。

除非另有说明,否则要求输入端口或输出端口作为参数的过程也接受输入/输出端口,即,输入/输出端口既是输入端口又是输出端口。

procedure: (make-input-port handler input-buffer) procedure: (make-output-port handler output-buffer) procedure: (make-input/output-port handler input-buffer output-buffer) returns: 一个新的文本端口 libraries: (chezscheme)

handler 必须是一个过程,input-buffer 和 output-buffer 必须是字符串。每个过程都创建一个通用端口。与端口关联的处理程序是 handler,输入缓冲区是 input-buffer, 输出缓冲区是 output-buffer. 对于 make-input-port,输出缓冲区未定义,而对于 make-output-port,输入缓冲区未定义。

输入或输入/输出端口的输入大小初始化为输入缓冲区的字符串长度,输入索引设置为 0. 输出或输入/输出端口的输出大小和索引也类似地初始化。

输入或输出缓冲区的长度可以为零,在这种情况下,缓冲实际上被禁用。

procedure: (port-handler port) returns: 一个过程 libraries: (chezscheme)

对于通用端口,port-handler 返回传递给上述通用端口创建过程之一的处理程序。对于由 open-input-file 和类似过程创建的端口,port-handler 返回一个内部处理程序,该处理程序可以用与任何其他处理程序相同的方式调用。

procedure: (port-input-buffer input-port) procedure: (port-input-size input-port) procedure: (port-input-index input-port) procedure: (textual-port-input-buffer textual-input-port) procedure: (textual-port-input-size textual-input-port) procedure: (textual-port-input-index textual-input-port) procedure: (binary-port-input-buffer binary-input-port) procedure: (binary-port-input-size binary-input-port) procedure: (binary-port-input-index binary-input-port) returns: 参见下文 libraries: (chezscheme)

这些过程返回输入端口的输入缓冲区,大小或索引。专用于文本或二进制端口的变体要比它们对应的通用版本略为高效。

procedure: (set-port-input-index! input-port n) procedure: (set-port-input-size! input-port n) procedure: (set-port-input-buffer! input-port x) procedure: (set-textual-port-input-index! textual-input-port n) procedure: (set-textual-port-input-size! textual-input-port n) procedure: (set-textual-port-input-buffer! textual-input-port string) procedure: (set-binary-port-input-index! binary-input-port n) procedure: (set-binary-port-input-size! binary-input-port n) procedure: (set-binary-port-input-buffer! binary-input-port bytevector) returns: unspecified libraries: (chezscheme)

过程 set-port-input-index! 将 input-port 的输入索引字段设置为 n,该字段必须是小于或等于端口输入大小的非负整数。

过程 set-port-input-size! 将 input-port 的输入大小字段设置为 n,该字段必须是小于或等于端口输入缓冲区字符串长度的非负整数。它还将输入索引设置为 0。 过程 set-port-input-buffer! 将 input-port 的输入缓冲区字段设置为 x,该字段必须是文本端口的字符串和二进制端口的字节向量。它还将输入大小设置为字符串或 bytevector 的长度,并将输入索引设置为 0。

专用于文本或二进制端口的变体要比它们对应的通用版本略为高效。

procedure: (port-input-count input-port) procedure: (textual-port-input-count textual-input-port) procedure: (binary-port-input-count binary-input-port) returns: 参见下文 libraries: (chezscheme)

这些过程返回一个精确的整数,表示要从端口的输入缓冲区中读取的剩余字符或字节数,即缓冲区大小和索引之间的差值。

专用于文本或二进制端口的变体要比它们对应的通用版本略为高效。

procedure: (port-input-empty? input-port) 返回: 如果端口的输入缓冲区不包含更多数据,则为 #t, 否则为 #f. libraries: (chezscheme)

此过程确定端口的输入计数是否为零,而不必计算或返回实际计数。

procedure: (port-output-buffer output-port) procedure: (port-output-size output-port) procedure: (port-output-index output-port) procedure: (textual-port-output-buffer output-port) procedure: (textual-port-output-size output-port) procedure: (textual-port-output-index output-port) procedure: (binary-port-output-buffer output-port) procedure: (binary-port-output-size output-port) procedure: (binary-port-output-index output-port) returns: 参见下文 libraries: (chezscheme)

这些过程返回输出端口的输出缓冲区,大小或索引。专用于文本或二进制端口的变体要比它们对应的通用版本略为高效。

procedure: (set-port-output-index! output-port n) procedure: (set-port-output-size! output-port n) procedure: (set-port-output-buffer! output-port x) procedure: (set-textual-port-output-index! textual-output-port n) procedure: (set-textual-port-output-size! textual-output-port n) procedure: (set-textual-port-output-buffer! textual-output-port string) procedure: (set-binary-port-output-index! output-port n) procedure: (set-binary-port-output-size! output-port n) procedure: (set-binary-port-output-buffer! binary-output-port bytevector) returns: unspecified libraries: (chezscheme)

过程 set-port-output-index! 将输出端口的输出索引字段设置为 n,该字段必须是小于或等于端口输出大小的非负整数。

过程 set-port-output-size! 将输出端口的输出大小字段设置为 n,该字段必须是小于或等于端口输出缓冲区的字符串长度的非负整数。它还将输出索引设置为 0。

过程 set-port-output-buffer! 将 output-port 的输出缓冲区字段设置为 x,该字段必须是文本端口的字符串和二进制端口的字节向量。它还将输出大小设置为字符串或字节向量的长度,并将输出索引设置为 0。

专用于文本或二进制端口的变体要比它们对应的通用版本略为高效。

procedure: (port-output-count output-port) procedure: (textual-port-output-count textual-output-port) procedure: (binary-port-output-count binary-output-port) returns: 参见下文 libraries: (chezscheme)

这些过程返回一个精确的整数,表示可在端口输出缓冲区中写入的以字符或字节计的空间量,即缓冲区大小和索引之间的差值。

专用于文本或二进制端口的变体要比它们对应的通用版本略为高效。

procedure: (port-output-full? output-port) 返回: 如果端口的输出缓冲区没有更多空间,则为 #t, 否则为 #f. libraries: (chezscheme)

此过程确定端口的输出计数是否为零,而不必计算或返回实际计数。

procedure: (mark-port-closed! port) returns: unspecified libraries: (chezscheme)

此过程直接把端口标记为关闭,从而禁止更多的输入或输出操作。它通常在接收到 close-port 消息时由处理程序使用。

procedure: (port-closed? port) 返回: 如果 port 已关闭,则为 #t, 否则为 #f. libraries: (chezscheme)

(let ([p (open-output-string)])
  (port-closed? p)) \Rightarrow #f

(let ([p (open-output-string)])
  (close-port p)
  (port-closed? p)) \Rightarrow #t

procedure: (set-port-bol! output-port obj) returns: unspecified libraries: (chezscheme)

当 obj 为#f 时,端口的行首(BOL)标志被清除; 否则,设置端口的 BOL 标志。

BOL 标志由 fresh-line(第 240 页)查询,以确定是否需要开启一个新行。对于文件输出端口,字符串输出端口和转录端口,此标志是自动维护的。新创建的文件和字符串输出端口会设置此标志,但使用 append 选项创建的文件输出端口除外,这种情况下,该标志会被重置。对于新创建的通用端口,会清除 BOL 标志,并且从不自动设置,但可以使用 set-port-bol!显式设置。在查询标志之前,总是立即刷新端口,因此不需要逐字符地维护缓冲端口。

procedure: (port-bol? port) 返回: 如果 port 的 BOL 标志已设置,则为 #t, 否则为 #f. libraries: (chezscheme)

procedure: (set-port-eof! input-port obj) returns: unspecified libraries: (chezscheme)

当 obj 不是#f 时,set-port-eof! 标记 input-port, 这样,一旦其缓冲区为空,即使底层字节或字符流中有更多数据可用,端口也会被视为处于 eof. 一旦这个人为制造的 eof 被读取,此 eof 标记就会被清除,使得流中的任何其他数据在 eof 之后可用。通用端口可以使用此功能来模拟由多个输入文件组成的流。

When obj is #f, the eof mark is cleared. 当 obj 为 #f 时,会清除 eof 标记。

The following example assumes /dev/zero provides an infinite stream of zero bytes. 以下示例假定/dev/zero 提供一个无限的零字节流。

(define p
  (parameterize ([file-buffer-size 3])
    (open-file-input-port "/dev/zero")))

(set-port-eof! p #t)
(get-u8 p) \Rightarrow #!eof
(get-u8 p) \Rightarrow 0
(set-port-eof! p #t)
(get-u8 p) \Rightarrow 0
(get-u8 p) \Rightarrow 0
(get-u8 p) \Rightarrow #!eof
(get-u8 p) \Rightarrow 0

procedure: (port-name port) 返回:与 port 关联的名称 libraries: (chezscheme)

The name may be any object but is usually a string or #f (denoting no name). For file ports, the name is typically a string naming the file. 名称可以是任何对象,但通常是字符串或#f(表示没有名称)。对于文件端口,名称通常是命名文件的字符串。

(let ([p (open-input-file "myfile.ss")])
  (port-name p)) \Rightarrow "myfile.ss"

(let ([p (open-output-string)])
  (port-name p)) \Rightarrow "string"

procedure: (set-port-name! port obj) returns: unspecified libraries: (chezscheme)

This procedure sets port's name to obj, which should be a string or #f (denoting no name). 此过程将 port 的名称设置为 obj,它应该是字符串或#f(表示没有名称)。

procedure: (port-length port) procedure: (file-length port) 返回:port 引用的文件或其他对象的长度 procedure: (port-has-port-length? port) 返回: 如果 port 支持 port-length,则为 #t, 否则为 #f. libraries: (chezscheme)

端口可能允许判定底层字符或字节流的长度。若是如此,过程 port-has-port-length? 返回#t,port-length 返回当前长度。对于二进制端口,长度始终是精确的非负整型字节数。对于文本端口,长度的表示形式是未指定的; 它可能不是一个精确的非负整数,即使它是,它也可能不代表字节或字符数。如果端口支持 set-port-length!,则可以在稍后的某个时间使用该长度来重置长度。如果在不支持 port-length 的端口上调用 port-length,则会抛出一个条件类型的 &assertion 异常。

对于 32 位版本的系统上的压缩文件,可能不会报告超过 232 的文件长度。

file-length 与 port-length 相同。

procedure: (set-port-length! port len) returns: unspecified procedure: (port-has-set-port-length!? port) 返回: 如果 port 支持 set-port-length!,则为 #t, 否则为 #f. libraries: (chezscheme)

端口可能允许设置底层字符或字节流的长度,即,扩展或缩短。若是如此,过程 port-has-set-port-length!? 返回 #t, 而 set-port-length! 改变长度。对于二进制端口,长度 len 必须是精确的非负整型字节数。对于文本端口,长度的表示形式是未指定的,如上面的 port-length 条目所述,但 len 必须是适用于文本端口的长度,通常只有在它是通过在同一端口上调用 port-length 而获得时,才能确保如此。如果 set-port-length! 在不支持它的端口上调用,则会抛出一个条件类型的 &assertion 异常。

无法将以压缩模式打开的端口长度设置到任意位置,并且尝试在 32 位版本的系统上将压缩文件的长度设置为超过 232 的结果是未定义的。

procedure: (port-nonblocking? port) 返回:如果端口处于非阻塞模式,则为 #t,否则为 #f procedure: (port-has-port-nonblocking?? port) 返回: 如果端口支持 port-nonblocking?, 则为 #t, 否则为 #f. libraries: (chezscheme)

端口可能允许判定端口的非阻塞状态。若是如此,过程 port-has-port-nonblocking?? 返回#t, 而 port-nonblocking? 返回一个布尔值,反映端口是否处于非阻塞模式。

procedure: (set-port-nonblocking! port obj) returns: unspecified procedure: (port-has-set-port-nonblocking!? port) 返回: 如果端口支持 set-port-nonblocking!, 则为 #t, 否则为 #f. libraries: (chezscheme)

端口可能允许以“非阻塞”方式执行读取或写入。若是如此,过程 port-has-set-port-nonblocking!? 返回#t, 而 set-port-nonblocking! 将端口设置为非阻塞模式(如果 obj 是真值)或阻塞模式(如果 obj 是#f)。如果 set-port-nonblocking! 在不支持它的端口上调用,则会抛出一个条件类型的 &assertion 异常。

默认情况下,通过标准的 R6RS 端口打开过程创建的端口,初始设置为阻塞模式。对于本文档中描述的大多数过程也是如此。基于非阻塞源的通用端口可能初始为非阻塞的。如果传入的文件描述符处于非阻塞模式,则 open-fd-input-port,open-fd-output-port 或 open-fd-input/output-port 返回的端口最初处于非阻塞模式。类似地,如果底层 stdin,stdout 或 stderr 文件描述符处于非阻塞模式,则 standard-input-port, standard-output-port, 或 standard-error-port 返回的端口最初处于非阻塞模式。

虽然 get-bytevector-some 和 get-string-some 通常不能返回空字节向量或空字符串,但如果端口处于非阻塞模式并且没有可用的输入,则它们可以。另外,get-bytevector-some! 和 get-string-some! 在端口处于非阻塞模式且没有可用数据时,则可能无法读取任何数据。类似地,如果端口处于非阻塞模式且没有可用空间,则 put-bytevector-some 和 put-string-some 可能无法写入任何数据。

Windows 不支持非阻塞模式。

procedure: (file-position port) procedure: (file-position port pos) returns: 参见下文 libraries: (chezscheme)

当省略第二个参数时,此过程的行为类似于 R6RS 的 port-position 过程,而当此参数存在时,类似于 R6RS 的 set-port-position! 过程。

对于使用压缩标志打开的压缩文件,file-position 返回未压缩数据流中的位置。更改使用压缩标志打开的压缩输入文件的位置通常需要回退和重读文件,因此可能很慢。使用压缩标志打开的压缩输出文件的位置只能向前移动; 这是通过写入一个(压缩的)零序列来完成的。 对于 32 位版本的系统上的压缩文件,可能不会报告超过 232 的文件位置。

procedure: (clear-input-port) procedure: (clear-input-port input-port) returns: unspecified libraries: (chezscheme)

如果未提供 input-port,则默认为当前输入端口。此过程会清除与 input-port 关联的缓冲区中的所有数据。这可能是必要的,例如,为了准备紧急查询,清除来自键盘的所有先前输入。

procedure: (clear-output-port) procedure: (clear-output-port output-port) returns: unspecified libraries: (chezscheme)

如果未提供 output-port,则默认为当前输出端口。此过程会清除与 output-port 关联的缓冲区中的所有数据。这可能是必要的,例如,为了准备紧急消息,清除交互端口上的任何待处理输出。

procedure: (flush-output-port) procedure: (flush-output-port output-port) returns: unspecified libraries: (chezscheme)

如果未提供 output-port,则默认为当前输出端口。此过程会强制立即打印与 output-port 关联的缓冲区中的所有数据。控制台输出端口在换行符之后及从控制台输入端口获得输入之前自动刷新; 所有端口在关闭时自动刷新。不过,flush-output-port 可能是必要的,以强制将没有换行符的消息发送到控制台输出端口或强制输出无延迟地出现在文件中。

procedure: (port-file-compressed! port) returns: unspecified libraries: (chezscheme)

port 必须是输入或输出端口,但不是输入/输出端口。它必须是指向常规文件的文件端口,即,磁盘上的文件而不是例如套接字等。端口可以是二进制或文本端口。如果端口是输出端口,则发送到端口的后续输出会被压缩。如果端口是输入端口,当且仅当端口当前指向压缩数据时,后续输入才会被解压缩。如果端口已设置为压缩,则此过程无效。

9.5. 字符串端口

字符串端口允许通过端口操作创建和操作字符串。过程 open-input-string 将字符串转换为文本输入端口,允许通过 read-char 或 read 等输入操作按顺序读取字符串中的字符。过程 open-output-string 允许使用 write-char 和 write 等输出操作构建新的字符串。

虽然字符串端口可以定义为通用端口,但它们被实现支持为原生端口。

procedure: (open-input-string string) returns: 新的字符串输入端口 libraries: (chezscheme)

字符串输入端口类似于文件输入端口,但从端口提取的字符和对象来自于字符串而不是文件。

当端口到达字符串末尾时,字符串端口位于“文件末尾”。没有必要关闭字符串端口,虽然可以这样做。

(let ([p (open-input-string "hi mom!")])
  (let ([x (read p)])
    (list x (read p)))) \Rightarrow (hi mom!)

procedure: (with-input-from-string string thunk) returns: thunk 的返回值 libraries: (chezscheme)

thunk 必须是一个过程,并且应该接受零个参数。with-input-from-string 将当前输入端口参数化为在应用 thunk 期间打开输入字符串的结果。

(with-input-from-string "(cons 3 4)"
  (lambda ()
    (eval (read)))) \Rightarrow (3 . 4)

procedure: (open-output-string) returns: 新的字符串输出端口 libraries: (chezscheme)

字符串输出端口类似于文件输出端口,除了写入端口的字符和对象放置在字符串(根据需要增长)而不是文件中。通过写入字符串输出端口构建的字符串可以使用 get-output-string 获得。请参阅下面给出的 get-output-string 示例。没有必要关闭字符串端口,虽然可以这样做。

procedure: (get-output-string string-output-port) 返回:与 string-output-port 关联的字符串 libraries: (chezscheme)

string-output-port 必须是 open-output-string 返回的端口。

作为副作用,get-output-string 重置 string-output-port,因此到 string-output-port 的后续输出被放入一个新字符串。

(let ([p (open-output-string)])
  (write 'hi p)
  (write-char #\space p)
  (write 'mom! p)
  (get-output-string p)) \Rightarrow "hi mom!"

可以使用字符串输出端口编写 format(第 9.13 节)的实现来生成字符串输出。

procedure: (with-output-to-string thunk) 返回:包含输出的字符串 libraries: (chezscheme)

thunk 必须是一个过程,并且应该接受零个参数。with-output-to-string 在应用 thunk 期间将当前输出端口参数化为新的字符串输出端口。如果 thunk 返回,则返回与新字符串输出端口关联的字符串,与 get-output-string 一样。

(with-output-to-string
  (lambda ()
    (display "Once upon a time ...")
    (newline))) \Rightarrow "Once upon a time ...\n"

9.6. 文件端口

thread parameter: file-buffer-size libraries: (chezscheme)

file-buffer-size 是一个参数对象,用于确定通过文件打开操作(例如,open-input-file 或 open-file-output-port)创建的端口的缓冲区模式不是 none 时,创建的每个缓冲区的大小。在不带参数调用时,该参数对象返回当前缓冲区大小。当使用正的 fixnum k 调用时,它将当前缓冲区的大小设置为 k。

procedure: (file-port? port) returns: #t if port is a file port, #f otherwise 返回: 如果 port 是文件端口,则为 #t, 否则为 #f. libraries: (chezscheme)

文件端口是直接基于 O/S 文件描述符的任何端口,例如,由 open-file-input-port,open-output-port,open-fd-input-port 等创建的端口,但不是字符串,字节向量,或自定义端口。

procedure: (port-file-descriptor port) 返回:与 port 关联的文件描述符 libraries: (chezscheme)

port 必须是文件端口,即,file-port? 返回 #t 的端口。

9.7. 自定义端口

thread parameter: custom-port-buffer-size libraries: (chezscheme)

custom-port-buffer-size 是一个参数对象,用于确定与新创建的自定义端口关联的缓冲区的大小。不带参数调用时,该参数对象返回当前缓冲区大小。 当使用正的 fixnum k 调用时,它将当前缓冲区的大小设置为 k。

9.8. 输入操作

global parameter: console-input-port libraries: (chezscheme)

console-input-port 是一个参数对象,用于确定服务器和交互式调试器使用的输入端口。当不带参数调用时,它返回控制台输入端口。 当使用一个参数(必须是文本输入端口)调用时,它会更改控制台输入端口的值。此参数对象的初始值是绑定到 Scheme 进程的标准输入(stdin)流的端口。

thread parameter: current-input-port libraries: (chezscheme)

current-input-port 是一个参数对象,用于确定大多数输入过程的默认端口参数,包括 read-char,peek-char 和 read。当不带参数调用时,current-input-port 返回当前输入端口。当使用一个参数(必须是文本输入端口)调用时,它会更改当前输入端口的值。Current-input-port 的 R6RS 版本只接受零个参数,即它不能用于更改当前输入端口。 此参数对象的初始值与 console-input-port 的初始值相同。

procedure: (open-input-file path) procedure: (open-input-file path options) returns: 新的输入端口 libraries: (chezscheme)

path 必须是一个字符串。open-input-file 为 path 命名的文件打开一个文本输入端口。如果文件不存在或无法打开以接收输入,则会抛出一个条件类型的 &i/o-filename 异常。

options(如果存在)是符号选项名称或选项列表。可能的符号选项名称是 compressed, uncompressed, buffered, 和 unbuffered. 选项列表是包含零个或多个符号选项名称的列表。

互斥的 compressed 和 uncompressed 选项决定输入文件被压缩时,是否应该解压缩。(请参见 open-output-file.)默认值是 uncompressed, 因此 uncompressed 选项仅作为文档使用。

互斥的 buffered 和 unbuffered 选项决定输入是否被缓冲。当缓冲输入时,它会以大块读取,并在内部进行缓冲以提高效率,从而减少操作系统请求的数量。当指定 unbuffered 选项时,输入是无缓冲的,但也不是完全如此,因为需要一个缓冲字符来支持 peek-char 和 unread-char. 默认情况下,输入是缓冲的,因此 buffered 选项仅作为文档使用。

For example, the call 例如,调用

(open-input-file "frob" '(compressed))

opens the file frob with decompression enabled. 打开文件 frob 时启用解压缩。

Open-input-file 的 R6RS 版本不支持可选的 options 参数。

procedure: (call-with-input-file path procedure) procedure: (call-with-input-file path procedure options) returns: procedure 的返回值 libraries: (chezscheme)

path 必须是一个字符串。procedure 应该接受一个参数。

call-with-input-file 为 path 命名的文件创建一个新的输入端口,就像使用 open-input-file 一样,并将此端口传递给 procedure。如果 procedure 正常返回,call-with-input-file 将关闭输入端口并返回 procedure 返回的值。

如果在 procedure 外部创建的 continuation 被调用,则 call-with-input-file 不会自动关闭输入端口,因为稍后可能会调用在 procedure 内创建的另一个 continuation,将控制权返回给 procedure。如果 procedure 没有返回,则只有在可以确认输入端口不再可访问的情况下,实现才可以自由关闭输入端口。如《Scheme 编程语言》第 4 版第 5.6 节所示,如果在 procedure 外部创建的 continuation 被调用,则 dynamic-wind 可用于确保端口被关闭。

See open-input-file above for a description of the optional options argument. 有关可选 options 参数的说明,请参阅上文的 open-input-file 条目。

The Revised6 Report version of call-with-input-file does not support the optional input argument. Call-with-input-file 的 R6RS 版本不支持可选的 input 参数。

procedure: (with-input-from-file path thunk) procedure: (with-input-from-file path thunk options) returns: thunk 的返回值 libraries: (chezscheme)

path 必须是一个字符串。thunk 必须是一个过程,并且应该接受零个参数。

with-input-from-file 在应用 thunk 期间,暂时将当前输入端口更改为打开 path 命名的文件的结果,就像使用 open-input-file 一样。如果 thunk 返回,则关闭端口并将当前输入端口恢复为其旧值。

如果在 thunk 返回之前调用在 thunk 之外创建的 continuation, 则 with-input-from-file 的行为是未定义的。实现可能会关闭端口并将当前输入端口恢复为其旧值—但它也可能不会这么做。

有关可选 options 参数的说明,请参阅上文的 open-input-file 条目。

with-input-from-file 的 R6RS 版本不支持可选的 options 参数。

procedure: (open-fd-input-port fd) procedure: (open-fd-input-port fd b-mode) procedure: (open-fd-input-port fd b-mode ?transcoder) 返回:文件描述符 fd 的新输入端口 libraries: (chezscheme)

fd 必须是非负的精确整数,并且应该是有效的打开文件描述符。如果存在 ?transcoder, 而且不为 #f,则它必须是一个编码转换器,并且此过程返回一个文本输入端口,其编码转换器为 ?transcoder. 否则,此过程返回二进制输入端口。有关其他参数的约束和影响的说明,请参见《Scheme 编程语言》第 4 版第 7.2 节中的引言。

The file descriptor is closed when the port is closed. 关闭端口时会关闭文件描述符。

procedure: (standard-input-port) procedure: (standard-input-port b-mode) procedure: (standard-input-port b-mode ?transcoder) 返回:连接到进程标准输入的新输入端口 libraries: (chezscheme)

如果存在 ?transcoder, 且不为#f,则它必须是编码转换器,并且该过程返回一个文本输入端口,其编码转换器是 ?transcoder. 否则,此过程返回二进制输入端口。缓冲模式 b-mode 默认为 block.

此过程的 R6RS 版本不接受可选的 b-mode 和 ?transcoder 参数,这将其限制为依赖于实现的缓冲模式(Chez Scheme 中为 block)和二进制输出。

procedure: (get-string-some textual-input-port) 返回:非空字符串或 eof 对象 libraries: (chezscheme)

如果 textual-input-port 位于文件末尾,则返回 eof 对象。否则,get-string-some 读取(就像使用 get-u8 一样)至少一个,或更多字符,并返回包含这些字符的字符串。端口的位置前进到读取的字符之后。此操作读取的最大字符数与实现有关。

如果端口处于非阻塞模式(请参阅 set-port-nonblocking!)且没有已就绪的输入,则会发生确保“至少一个字符”的异常。在这种情况下,返回一个空字符串。

procedure: (get-string-some! textual-input-port string start n) 返回:读取的字符数,为精确的非负整数,或 eof 对象 libraries: (chezscheme)

start 和 n 必须是精确的非负整数,start 和 n 的和不得超过 string 的长度。

如果 n 为 0,则此过程返回零,而不尝试从 textual-input-port 读取,并且不修改 string.

否则,如果 textual-input-port 位于文件末尾,则此过程返回 eof 对象,但在端口处于非阻塞模式(请参阅 set-port-nonblocking!),并且不利用阻塞无法确定端口位于文件结尾时,它返回 0. 在两种情况下,它都不会修改 string.

否则,此过程从端口读取(和使用 get-char 一样)最多 n 个字符,将字符存储在 string 始于 start 的连续位置上,把端口位置前进到读取的字符之后,并返回读取的字符数。

如果端口处于非阻塞模式,则此过程不会读取比它不使用阻塞时能够读取的更多字符,因此可能读取零个字符; 否则,它会读取至少一个字符,但不会超过当第一个字符变为可用时可用的字符。

procedure: (get-bytevector-some! binary-input-port bytevector start n) 返回:读取的字节数,为精确的非负整数或 eof 对象 libraries: (chezscheme)

start 和 n 必须是精确的非负整数,start 和 n 的和不得超过 bytevector 的长度。

如果 n 为 0,则此过程返回零,而不尝试从 binary-input-port 读取并且不修改 bytevector.

否则,如果 binary-input-port 位于文件末尾,则此过程返回 eof 对象,但在端口处于非阻塞模式(请参阅 set-port-nonblocking!),并且不利用阻塞无法确定端口位于文件结尾时,它返回 0. 在两种情况下,它都不会修改 bytevector.

否则,此过程从端口读取(如同使用 get-u8)最多 n 个字节,将字节存储在 bytevector 始于 start 的连续位置上,把端口的位置前进到读取的字节之后,并返回读取的字节数。

如果端口处于非阻塞模式,则此过程不会读取比它不使用阻塞时能够读取的更多字节,因此可能读取零个字节; 否则,它会读取至少一个字节,但不会超过当第一个字节变为可用时可用的字节。

procedure: (unread-char char) procedure: (unread-char char textual-input-port) procedure: (unget-char textual-input-port char) returns: unspecified libraries: (chezscheme)

对于 unread-char,如果未提供 textual-input-port,则默认为当前输入端口。这些过程“未读”从 textual-input-port 读取的最后一个字符。char 可能会也可能不会被忽略,具体取决于实现。 在任何情况下,char 应该是从端口读取的最后一个字符。如果没有对 read-char 或 get-char 进行中间调用,则一个字符不应在同一端口上被未读两次。

unread-char 和 unget-char 是为需要一个前瞻字符的应用提供的,可用于代替,甚至与 peek-char 或 lookahead-char 结合使用。过程 read-word 需要一个前瞻字符,它基于 unread-char 定义如下。read-word 将文本输入端口中的下一个单词作为字符串返回,其中单词被定义为一个字母字符序列。由于直到它读取到一个多余字符后,才能知道读完了整个单词,因此 read-word 使用 unread-char 将那个字符返还给输入端口。

(define read-word
  (lambda (p)
    (list->string
      (let f ([c (read-char p)])
        (cond
          [(eof-object? c) '()]
          [(char-alphabetic? c)
           (cons c (f (read-char p)))]
          [else
           (unread-char c p)
           '()])))))

在下面的替代版本中,使用 peek-char 而不是 unread-char.

(define read-word
  (lambda (p)
    (list->string
      (let f ([c (peek-char p)])
        (cond
          [(eof-object? c) '()]
          [(char-alphabetic? c)
           (read-char p)
           (cons c (f (peek-char p)))]
          [else '()])))))

在这种情况下,unread-char 的优势是每个单词只需要调用一次 unread-char,而对于单词中的每个字符以及单词之后的第一个字符,都需要调用 peek-char。在许多情况下,unread-char 和 unget-char 不享有此优势,则应该转而使用 peek-char 或 lookahead-char。

procedure: (unget-u8 binary-input-port octet) returns: unspecified libraries: (chezscheme)

此过程“回退”从 binary-input-port 读取的最后一个字节。octet 可能会也可能不会被忽略,具体取决于实现。在任何情况下,octet 应该是从端口读取的最后一个字节。如果没有对 get-u8 的中间调用,则不应在同一端口上回退一个字节两次。

procedure: (input-port-ready? input-port) 返回: 如果 input-port 上有可用数据,则为 #t, 否则为 #f. libraries: (chezscheme)

input-port-ready? 允许程序在不挂起的情况下检查输入是否在文本或二进制输入端口上可用。如果输入可用或端口位于文件末尾,则 input-port-ready? 返回#t. 如果它无法确定端口的输入是否就绪,则会抛出一个条件类型的 i/o-read-error 异常。否则,它返回#f.

procedure: (char-ready?) procedure: (char-ready? textual-input-port) 返回: 如果 textual-input-port 上有可用字符,则为 #t, 否则为 #f. libraries: (chezscheme)

如果未提供 textual-input-port,则默认为当前输入端口。char-ready? 类似于 input-port-ready?, 只是它仅限于文本输入端口。

procedure: (block-read textual-input-port string) procedure: (block-read textual-input-port string count) returns: 参见下文 libraries: (chezscheme)

count 必须是小于或等于 string 长度的非负 fixnum. 如果未提供,则默认为 string 的长度。

如果 textual-input-port 位于文件末尾,则返回 eof 对象。否则,会以 textual-input-port 中可读的至多 count 个字符填充 string, 并返回置于字符串中的字符数。

如果缓冲了 textual-input-port 并且缓冲区是非空的,则返回缓冲的输入或其一部分; 否则 block-read 完全绕过缓冲区。

procedure: (read-token) procedure: (read-token textual-input-port) returns: 参见下文 libraries: (chezscheme)

解析 Scheme datum 在概念上分两步执行。首先,形成 datum 的字符序列被分组为 token, 例如符号,数字,左括号和双引号。在这第一步中,空格和注释会被丢弃。接着,这些 token 被分组为数据。

读取执行这两个步骤并创建它解析的每个 datum 的内部表示。read-token 可用于仅执行第一步,一次一个 token. read-token 旨在供编辑器和程序格式化工具使用,它们必须能够解析程序或 datum 而无需实际读取它。

如果未提供 textual-input-port,则默认为当前输入端口。它从输入端口读取一个 token 并返回四个值:

type: 描述读取的 token 类型的符号,

value: token 的值,

start: token 的第一个字符相对于输入端口的起始位置的位置,和 end: token 之后的第一个位置,相对于输入端口的起始位置。

当 token 类型完全指定 token 时,read-token 返回#f 作为值。下面列出了 token 类型,括号中有相应的值。

atomic (atom) 一个原子值,即,符号,布尔值,数字,字符,#!eof 或#!bwp

box (#f) box 前缀,即, #& dot (#f) 点对分隔符,即, . eof (#!eof) 文件末尾 fasl (#f) fasl 前缀,即, #@ insert (n) 图引用,即, #n# lbrack (#f) 左方括号 lparen (#f) 左圆括号 mark (n) 图标记,即, #n= quote (quote, quasiquote, syntax, unquote, unquote-splicing, or datum-comment) 缩写标记,例如, ' 或 ,@ 或 datum 注释前缀 rbrack (#f) 右方括号 record-brack (#f) record 左方括号,即, #[ rparen (#f) 右圆括号 vfxnparen (n) fxvector 前缀,即, #nvfx( vfxparen (#f) fxvector 前缀,即, #vfx( vnparen (n) 向量前缀,即, #n( vparen (#f) 向量前缀,即, #( vu8nparen (n) 字节向量前缀,即, #nvu8( vu8paren (#f) 字节向量前缀,即, #vu8(

在将来的系统版本中,token 类型集可能会发生变化; 有关此类更改的详细信息,请查看发行说明。

输入端口指向 token 之后的第一个字符位置,即从起始位置开始的结束字符。

(define s (open-input-string "(a b c)"))
(read-token s) \Rightarrow lparen
                   #f
                   0
                   1
(define s (open-input-string "abc 123"))
(read-token s) \Rightarrow atomic
                   abc
                   0
                   3
(define s (open-input-string ""))
(read-token s) \Rightarrow eof
                   #!eof
                   0
                   0
(define s (open-input-string "#7=#7#"))
(read-token s) \Rightarrow mark
                   7
                   0
                   3
(read-token s) \Rightarrow insert
                   7
                   3
                   6

read-token 返回的信息并不总是足以重构构成 token 的精确字符序列。例如,1.0 和 1e0 都返回类型为 atomic 的值 1.0. 只有通过重新定位端口并使用通过 start 和 end 给出的相对位置读取适当长度的字符块,才能获得精确的字符序列。

9.9. 输出操作

global parameter: console-output-port libraries: (chezscheme)

console-output-port 是一个参数对象,用于确定服务器和交互式调试器使用的输出端口。当不带参数调用时,它返回控制台输出端口。当使用一个参数(必须是文本输出端口)调用时,它会更改控制台输出端口的值。此参数对象的初始值是绑定到 Scheme 进程的标准输出(stdout)流的端口。

thread parameter: current-output-port libraries: (chezscheme)

current-output-port 是一个参数对象,用于确定大多数输出过程的默认端口参数,包括 write-char,newline,write,display 和 pretty-print. 当不带参数调用时,current-output-port 返回当前输出端口。当使用一个参数(必须是文本输出端口)调用时,它会更改当前输出端口的值。Current-output-port 的 R6RS 版本只接受零个参数,即它不能用于更改当前输出端口。此参数对象的初始值与 console-output-port 的初始值相同。

thread parameter: console-error-port libraries: (chezscheme)

console-error-port 是一个参数对象,可用于设置或获取控制台错误端口,该端口确定默认异常处理程序将条件和其他消息打印到的端口。在不带参数调用时,console-error-port 将返回控制台错误端口。当使用一个参数(必须是文本输出端口)调用时,它会更改控制台错误端口的值。

如果系统确定标准输出(stdout)和标准错误(stderr)流引用了相同的文件,套接字,管道,虚拟终端,设备等,则此参数对象初始化为与 console-output-port 相同的值。否则,此参数对象初始化为与 Scheme 进程的标准错误(stderr)流相关联的一个不同端口。

thread parameter: current-error-port libraries: (chezscheme)

current-error-port 是一个可用于设置或获取当前错误端口的参数对象。当不带参数调用时,current-error-port 返回当前错误端口。当使用一个参数(必须是文本输出端口)调用时,它会更改当前错误端口的值。Current-error-port 的 R6RS 版本只接受零个参数,即它不能用于更改当前错误端口。此参数对象的初始值与 console-error-port 的初始值相同。

procedure: (open-output-file path) procedure: (open-output-file path options) returns: 新的输出端口 libraries: (chezscheme)

path 必须是一个字符串。open-output-file 为 path 命名的文件打开文本输出端口。

options(如果存在的话)是一个符号选项名或选项列表。可能的符号选项名是 error,truncate,replace,append,compressed,uncompressed,buffered,unbuffered,exclusive 和 nonexclusive。选项列表是包含零个或多个符号选项名以及可能的双元素选项 mode mode 的列表。

互斥的 error,truncate,replace 和 append 选项用于指示当要打开的文件已存在时会发生什么。

error: 抛出一个条件类型的&i/o-filename 异常。 replace: 在打开新文件之前删除现有文件。 truncate: 打开现有文件,并将其长度截短为 0. append: 在每次写入之前打开现有文件并将输出端口置于文件末尾,从而始终将发送到端口的输出附加到文件末尾。

默认行为是抛出异常。

互斥的 compressed 和 uncompressed 选项确定是否要压缩输出文件。使用由 Jean-loup Gailly 和 Mark Adler 开发的 zlib 压缩库来执行压缩。 因此它与 gzip 程序兼容,这意味着 gzip 可用于解压缩由 Chez Scheme 生成的文件,反之亦然。默认情况下,文件是未压缩的,因此 uncompressed 选项仅作为文档使用。

互斥的 buffered 和 unbuffered 选项确定输出是否被缓冲。无缓冲输出立即发送到文件,而缓冲输出不会写入,直到端口的输出缓冲区被填满或端口被刷新(通过 flush-output-port)或关闭(通过 flush-output-port 或在端口不再可访问时通过存储管理系统)。默认情况下,输出会被缓冲以提高效率,因此 buffered 选项仅作为文档使用。

互斥的 exclusive 和 nonexclusive 选项决定了对文件的访问是否是“独占的”。指定独占选项后,文件将被锁定,直到端口关闭,以阻止其他进程访问。在某些系统上,锁是建议性的,即,只有当其它进程也试图独占打开文件时,它才会禁止它们访问。非独占访问是默认选项,因此 nonexclusive 选项仅作为文档有用。

mode 选项决定此操作创建文件时在 Unix 系统上的权限位,具体取决于进程 umask。选项列表中的后续元素必须是一个精确的整数,以 Unix open 函数的方式指定权限。Windows 下会忽略 mode 选项。

例如,调用

(open-output-file "frob" '(compressed truncate mode #o644))

打开文件 frob 并启用压缩。如果 frob 已经存在则会被截短。在基于 Unix 的系统上,如果 frob 尚不存在,则新创建的文件上的权限位将被设置为#o644 和进程的 umask 的逻辑与。

Open-output-file 的 R6RS 版本不支持可选的 options 参数。

procedure: (call-with-output-file path procedure) procedure: (call-with-output-file path procedure options) returns: procedure 的返回值 libraries: (chezscheme)

path 必须是一个字符串。程序应该接受一个参数。

call-with-output-file 为 path 命名的文件创建一个新的输出端口,就像使用 open-output-file 一样,并将此端口传递给 procedure. 如果 procedure 返回,call-with-output-file 将关闭输出端口并返回 procedure 返回的值。

如果在 procedure 外部创建的 continuation 被调用,则 call-with-output-file 不会自动关闭输出端口,因为稍后可能会调用在 procedure 内创建的另一个 continuation,将控制权返回给 procedure。如果 procedure 没有返回,则只有在可以确认输出端口不再可访问的情况下,实现才可以自由关闭输出端口。如《Scheme 编程语言》第 4 版第 5.6 节所示,如果在 procedure 外部创建的 continuation 被调用,则 dynamic-wind 可用于确保端口被关闭。

有关可选 options 参数的说明,请参阅上面的 open-output-file.

Call-with-output-file 的 R6RS 版本不支持可选的 options 参数。

procedure: (with-output-to-file path thunk) procedure: (with-output-to-file path thunk options) returns: thunk 的返回值 libraries: (chezscheme)

path 必须是一个字符串。thunk 必须是一个过程,并且应该接受零个参数。

with-output-from-file 在应用 thunk 期间,暂时将当前输出端口重绑定为打开 path 命名的文件的结果,就像使用 open-output-file 一样。如果 thunk 返回,则关闭端口并将当前输出端口恢复为其旧值。

如果在 thunk 返回之前调用在 thunk 之外创建的 continuation, 则 with-output-from-file 的行为是未定义的。实现可能会关闭端口并将当前输出端口恢复为其旧值—但它也可能不会这么做。

有关可选 options 参数的说明,请参阅上面的 open-output-file.

with-output-to-file 的 R6RS 版本不支持可选的 options 参数。

procedure: (open-fd-output-port fd) procedure: (open-fd-output-port fd b-mode) procedure: (open-fd-output-port fd b-mode ?transcoder) 返回:文件描述符 fd 的新输出端口 libraries: (chezscheme)

fd 必须是非负的精确整数,并且应该是有效的打开文件描述符。如果存在 ?transcoder, 而且不为 #f,则它必须是一个编码转换器,并且此过程返回一个文本输出端口,其编码转换器为 ?transcoder. 否则,此过程返回二进制输出端口。有关其他参数的约束和影响的说明,请参见《Scheme 编程语言》第 4 版第 7.2 节中的引言。

关闭端口时会关闭文件描述符。

procedure: (standard-output-port) procedure: (standard-output-port b-mode) procedure: (standard-output-port b-mode ?transcoder) 返回:连接到进程的标准输出的新输出端口 libraries: (chezscheme)

如果存在 ?transcoder, 且不为#f,则它必须是编码转换器,并且该过程返回一个文本输出端口,其编码转换器是 ?transcoder. 否则,此过程返回二进制输出端口。缓冲模式 b-mode 默认为 line, 在 Chez Scheme 中,它与 block 仅在针对文本输出端口时有区别。

此过程的 R6RS 版本不接受可选的 b-mode 和 ?transcoder 参数,这将其限制为依赖于实现的缓冲模式(Chez Scheme 中为 line)和二进制输出。

procedure: (standard-error-port) procedure: (standard-error-port b-mode) procedure: (standard-error-port b-mode ?transcoder) 返回:连接到进程的标准错误的新输出端口 libraries: (chezscheme)

如果存在 ?transcoder, 且不为#f,则它必须是编码转换器,并且该过程返回一个文本输出端口,其编码转换器是 ?transcoder. 否则,此过程返回二进制输出端口。缓冲模式 b-mode 默认为 none. 有关其他参数的约束和影响的说明,请参见《Scheme 编程语言》第 4 版第 7.2 节中的引言。

此过程的 R6RS 版本不接受可选的 b-mode 和 ?transcoder 参数,这将其限制为依赖于实现的缓冲模式(Chez Scheme 中为 none)和二进制输出。

procedure: (put-bytevector-some binary-output-port bytevector) procedure: (put-bytevector-some binary-output-port bytevector start) procedure: (put-bytevector-some binary-output-port bytevector start n) returns: 写入的字节数 libraries: (chezscheme)

start 和 n 必须是非负的精确整数,start 和 n 的和不得超过 bytevector 的长度。如果未提供,则 start 默认为零,而 n 默认为 bytevector 的长度和 start 之间的差值。

此过程通常将 bytevector 始于 start 的 n 个字节写入端口,并将其位置前进到写入的字节末尾之后。不过,如果端口处于非阻塞模式(请参阅 set-port-nonblocking!),而若系统必须阻塞才能写入更多字节,则写入的字节数可能小于 n.

procedure: (put-string-some textual-output-port string) procedure: (put-string-some textual-output-port string start) procedure: (put-string-some textual-output-port string start n) returns: 写入的字符数 libraries: (chezscheme)

start 和 n 必须是非负的精确整数,start 和 n 的和不得超过 string 的长度。如果未提供,则 start 默认为零,而 n 默认为 string 的长度和 start 之间的差值。

此过程通常将 string 始于 start 的 n 个字符写入端口,并将其位置前进到写入的字符末尾之后。不过,如果端口处于非阻塞模式(请参阅 set-port-nonblocking!),而若系统必须阻塞才能写入更多字符,则写入的字符数可能小于 n.

procedure: (display-string string) procedure: (display-string string textual-output-port) returns: unspecified libraries: (chezscheme)

如果未指定 textual-output-port,display-string 将把 string 中包含的字符写入 textual-output-port 或当前输出端口。封闭字符串的引号不会被打印,也不会转义字符串中的特殊字符。对于字符串的显示,display-string 是对 display 的一个更有效的替代。

procedure: (block-write textual-output-port string) procedure: (block-write textual-output-port string count) returns: unspecified libraries: (chezscheme)

count 必须是小于或等于 string 长度的非负 fixnum. 如果未提供,则默认为 string 的长度。

block-write 将 string 的前 count 个字符写入 textual-output-port. 如果端口是缓冲的并且缓冲区非空,则会在写入 string 的内容之前刷新缓冲区。在所有情况下,string 的内容都会立即写入,而不会通过缓冲区。

procedure: (truncate-port output-port) procedure: (truncate-port output-port pos) procedure: (truncate-file output-port) procedure: (truncate-file output-port pos) returns: unspecified libraries: (chezscheme)

truncate-port 和 truncate-file 是完全相同的。

pos 必须是一个精确的非负整数。它默认为 0.

这些过程将与 output-port 相关联的文件或其他对象截短到 pos 位置,并将端口重新定位到该位置,即它结合了 set-port-length! 和 set-port-position! 的功能,并且只有在 port-has-set-port-length!? 和 port-has-set-port-position!? 对于端口都为真时,才能在端口上调用它。

procedure: (fresh-line) procedure: (fresh-line textual-output-port) returns: unspecified libraries: (chezscheme)

如果未提供 textual-output-port,则默认为当前输出端口。

此过程的行为类似于 newline, 即,发送一个换行符到 textual-output-port,除非它可以确定该端口已经位于一行的开头。它通过刷新端口并查询与端口关联的“行首”(BOL)标志来实现此目的。(参见第 220 页)

9.10. 输入/输出操作

procedure: (open-input-output-file path) procedure: (open-input-output-file path options) returns: 新的输入-输出端口 libraries: (chezscheme)

path 必须是一个字符串。open-input-output-file 为 path 命名的文件打开一个文本输入-输出端口。

该端口可用于读取或写入指定的文件。如果文件尚不存在,则创建该文件。

options(如果存在的话)是一个符号选项名或选项列表。可能的符号选项名是 buffered, unbuffered, exclusive, 和 nonexclusive. 选项列表是包含零个或多个符号选项名以及可能的双元素选项 mode mode 的列表。有关这些选项的说明,请参阅 open-output-file 的描述。

输入/输出文件通常使用 close-port 关闭,但也可以使用 close-input-port 或 close-output-port 关闭。

procedure: (open-fd-input/output-port fd) procedure: (open-fd-input/output-port fd b-mode) procedure: (open-fd-input/output-port fd b-mode ?transcoder) returns: a new input/output port for the file descriptor fd libraries: (chezscheme)

fd 必须是非负的精确整数,并且应该是有效的打开文件描述符。如果存在 ?transcoder, 而且不为 #f,则它必须是一个编码转换器,并且此过程返回一个文本输入/输出端口,其编码转换器为 ?transcoder. 否则,此过程返回二进制输入/输出端口。有关其他参数的约束和影响的说明,请参见《Scheme 编程语言》第 4 版第 7.2 节中的引言。

关闭端口时会关闭文件描述符。

9.11. 非 Unicode 字节向量/字符串转换

本节中描述的过程将包含非 Unicode 字符集中的单字节和多字节序列的字节向量与 Scheme 字符串进行转换。它们仅在 Windows 下可用。在其他操作系统下,以及当 Windows 下有可用的 iconv DLL 时,bytevector->string 和 string->bytevector 可以与基于通过 iconv-codec 构造的编解码器的编码转换器一起使用,以实现相同的结果,并且可以更好地控制对无效字符和行尾的处理。

procedure: (multibyte->string code-page bytevector) returns: 一个字符串,包含编码在 bytevector 中的字符 procedure: (string->multibyte code-page string) returns: 一个字节向量,包含 string 中字符的编码 libraries: (chezscheme)

这些过程仅在 Windows 下可用。过程 multibyte->string 是 Windows API MultiByteToWideChar 函数的封装,而 string->multibyte 是 Windows API WideCharToMultiByte 函数的封装。

code-page 声明了输入或输出字节向量中字节序列的编码。它必须是一个精确的非负整数,用于标识代码页或符号 cp-acp,cp-maccp,cp-oemcp,cp-symbol,cp-thread-acp,cp-utf7 或 cp-utf8 之一,它们与类似名称常量的 API 函数含义相同。

9.12. 美观打印

美观打印器是 write 过程的一个版本,它通过引入空白字符,即换行和缩进,生成可读性更好的输出。美观打印器是读-写-打印循环(服务员)使用的默认打印器,用于打印每个求值形式的输出。也可以通过调用过程 pretty-print 显式调用美观打印器。

美观打印器的操作可以通过本节后面介绍的 pretty-format 过程进行控制,该过程允许程序员指定特定形式的打印方式,本节后面还介绍了多种 pretty-printer 的控制方式,以及如何通过 9.14 节中描述的通用输入/输出控制。

procedure: (pretty-print obj) procedure: (pretty-print obj textual-output-port) returns: unspecified libraries: (chezscheme)

如果未提供 textual-output-port,则默认为当前输出端口。

pretty-print 类似于 write,除了它使用任意数量的空格和换行符以便以一种悦目的样式打印 obj,例如,它通过缩进显示嵌套级别。

(pretty-print '(define factorial (lambda (n) (let fact ((i n) (a 1))
  (if (= i 0) a (fact (- i 1) (* a i)))))))

可以生成

(define factorial
  (lambda (n)
    (let fact ([i n] [a 1])
      (if (= i 0) a (fact (- i 1) (* a i))))))

procedure: (pretty-file ifn ofn) returns: unspecified libraries: (chezscheme)

ifn 和 ofn 必须是字符串。pretty-file 从 ifn 命名的文件中依次读取各个对象,并将该对象美观打印到由 ofn 命名的文件中。输入中存在的注释将被读取器丢弃,因此不会出现在输出文件中。如果 ofn 命名的文件已存在,则替换它。

procedure: (pretty-format sym) returns: 参见下文 procedure: (pretty-format sym fmt) returns: unspecified libraries: (chezscheme)

默认情况下,美观打印器使用通用算法打印各个形式。此过程用于覆盖此默认值并指导 pretty-printer 对特定形式的处理。符号 sym 命名一个语法形式或过程。只传入一个参数时,pretty-format 返回与 sym 关联的当前格式,如果没有格式与 sym 关联,则返回#f.

传入两个参数时,会把格式 fmt 与 sym 相关联,以用于将来对美观打印器的调用。fmt 必须采用下面描述的格式化语言。

→ (quote symbol)

var
symbol
(read-macro string symbol)
(meta)
(bracket . fmt-tail)
(alt fmt fmt\*)
fmt-tail

fmt-tail → ()

(tab fmt …)
(fmt tab …)
(tab fmt . fmt-tail)
(fmt …)
(fmt . fmt-tail)
(fill tab fmt …)

tab → int

#f

当存在多个备选方案时,一些格式形式用于匹配,而其他格式形式用于匹配和控制缩进或打印。下面给出每个 fmt 的描述。

(quote symbol): 只匹配符号 symbol.

var: 匹配任意符号。

symbol: 匹配任意输入。

(read-macro string symbol): 用于读取宏,如 quote 和 syntax. 它匹配此形式(符号子形式)的任何输入。对于匹配的形式,美观打印器会打印紧跟着子形式的字符串。

(meta): 这是一个用于 meta 关键字的特殊情况(第 11.8 节),它用作另一个形式的关键字前缀。

(alt fmt fmt*): 这会将输入与指定格式进行比较,并使用最接近的格式。大多数情况下,其中一种格式会完全匹配,但在其他情况下,如输入有错误或出现在语法抽象模板中的抽象形式中时,没有格式能完全匹配。

(bracket . fmt-tail): 匹配任何列表结构的输入并打印括在方括号中的输入,即, [ and ], 而不是圆括号。

fmt-tail: 匹配任意列表结构的输入。

列表结构形式的缩进是通过上面最后两种情况中使用的 fmt-tail 说明符确定的。下面给出每个 fmt-tail 的描述。

(): 匹配空列表尾部。

(tab fmt …): This matches the tail of any proper list; if the tail is nonempty and the list does not fit entirely on the current line, a line break is inserted before the first subform of the tail and tab (参见下文) determines the amount by which this and all subsequent subforms are indented.

(fmt tab …): 匹配任意完全列表的尾部; 如果尾部不为空并且列表不能完全放入当前行,则在尾部的第一个子形式之后插入一个换行符,而所有后续子形式的缩进量由 tab(参见下文)决定。

(tab fmt . fmt-tail): 如果尾部的尾部匹配 fmt-tail, 则此形式匹配一个非空尾部。如果列表不能完全放入当前行,则在尾部的第一个子形式之前插入一个换行符,而子形式的缩进量由 tab(参见下文)决定。

(fmt …): 匹配任意完全列表的尾部,并指定,在当前或后续的子形式之前或之后不插入任何换行符。

(fmt . fmt-tail): 如果尾部的尾部匹配 fmt-tail, 则此形式匹配一个非空尾部,并指定,在当前子形式之前或之后不插入任何换行符。

(fill tab fmt …): 匹配任意完全列表的尾部,并调用填充模式,在此模式下,每行会填入尽可能多的内容。

tab 决定列表子形式的缩进量。如果 tab 是非负的精确整数 int,则子形式从父形式的左圆括号或左方括号之后的字符位置缩进 int 个空格。如果 tab 是#f,则使用标准缩进。可以通过参数对象 pretty-standard-indent 确定或更改标准缩进,本节稍后将对此进行介绍。

如果给出的格式不完全匹配,那么美观打印器会尝试尽可能使用给定的格式。例如,如果格式与具有特定数量子形式的列表结构形式匹配,但是给出的子形式偏多或偏少,则美观打印器将根据需要丢弃或复制子形式格式。

这是一个示例,显示 let 的格式可能已指定。

(pretty-format 'let
  '(alt (let ([bracket var x] 0 ...) #f e #f e ...)
        (let var ([bracket var x] 0 ...) #f e #f e ...)))

由于 let 有两种形式,命名的和未命名的,因此指定了两种备选形式。在任何一种情况下, bracket fmt 用于将绑定封装在方括号中,如果一行中放不下所有绑定,则其中第一个绑定之后的所有绑定显示在第一个绑定的正下方(且紧跟在外围的圆括号之后)。每个 body 形式以标准缩进缩进。

thread parameter: pretty-line-length thread parameter: pretty-one-line-limit libraries: (chezscheme)

这些参数对象的值都必须是正的 fixnum.

参数对象 pretty-line-length 和 pretty-one-line-limit 控制 pretty-print 产生的输出。pretty-line-length 决定美观打印器会在一行中的哪个字符位置(从第一个开始)之后尝试截断输出。这只是一个软限制;如果有必要,pretty-printer 会超出 pretty-line-length 的限制。

pretty-one-line-limit 类似于 pretty-line-length, 只是它是相对于输出中每行的第一个非空白位置计算。这也是一个软限制。

thread parameter: pretty-initial-indent libraries: (chezscheme)

此参数对象的值必须是非负 fixnum.

参数对象 pretty-initial-indent 用于指示 pretty-print 在输出行上调用它的位置。如果 pretty-initial-indent 为零(默认值),则 pretty-print 假定它产生的第一行输出将从行的开头开始。如果设置为非零值 n,则 pretty-print 假定第一行将出现在字符位置 n,并将调整其后续行的打印。

thread parameter: pretty-standard-indent libraries: (chezscheme)

此参数对象的值必须是非负 fixnum.

它决定 pretty-print 对大多数形式(比如 let 表达式)的子表达式,从形式的关键字或第一个子表达式开始的缩进量。

thread parameter: pretty-maximum-lines libraries: (chezscheme)

参数对象 pretty-maximum-lines 控制 pretty-print 被调用时其打印的行数。如果设置为#f(默认值),则不施加限制; 如果设置为非负的 fixnum n, 则最多打印 n 行。

9.13. 格式化输出

procedure: (format format-string obj ...) procedure: (format #f format-string obj ...) procedure: (format #t format-string obj ...) procedure: (format textual-output-port format-string obj ...) returns: 参见下文 libraries: (chezscheme)

When the first argument to format is a string or #f (first and second forms above), format constructs an output string from format-string and the objects obj …. Characters are copied from format-string to the output string from left to right, until format-string is exhausted. The format string may contain one or more format directives, which are multi-character sequences prefixed by a tilde ( ~ ). Each directive is replaced by some other text, often involving one or more of the obj … arguments, as determined by the semantics of the directive. 当 format 的第一个参数是一个字符串或#f(上面的第一个和第二个形式)时,format 以 format-string 和对象 obj… 构造一个输出字符串。字符从 format-string 从左到右复制到输出字符串,直到读完 format-string. 格式字符串可以包含一个或多个格式指令,这些指令是以波浪号(~)为前缀的多字符序列。每个指令都被其他一些文本替换,通常涉及一个或多个 obj …参数,具体由指令的语义决定。

When the first argument is #t, output is sent to the current output port instead, as with printf. When the first argument is a port, output is sent to that port, as with fprintf. printf and fprintf are described later in this section. 当第一个参数是#t 时,输出将被发送到当前输出端口,和使用 printf 一样。当第一个参数是端口时,输出将发送到该端口,和使用 fprintf 一样。printf 和 fprintf 将在本节后面介绍。

Chez Scheme's implementation of format supports all of the Common Lisp [30] format directives except for those specific to the Common Lisp pretty printer. Please consult a Common Lisp reference or the Common Lisp Hyperspec, for complete documentation. A few of the most useful directives are described below. format 的 Chez Scheme 实现支持所有 Common Lisp [30]格式指令,除了那些专用于 Common Lisp 美观打印器的指令。完整文档请参阅 Common Lisp 参考或 Common Lisp Hyperspec. 下面介绍了一些最有用的指令。

Absent any format directives, format simply displays its string argument. 如果没有任何格式指令,format 便直接显示其字符串参数。

(format "hi there") \Rightarrow "hi there"

The ~s directive is replaced by the printed representation of the next obj, which may be any object, in machine-readable format, as with write. ~s 指令由下一个 obj 的机器可读格式的打印形式替换,和使用 write 一样,obj 可以是任何对象。

(format "hi ~s" 'mom) \Rightarrow "hi mom"
(format "hi ~s" "mom") \Rightarrow "hi \"mom\""
(format "hi ~s~s" 'mom #\!) \Rightarrow "hi mom#\\!"

The general form of a s directive is actually ~mincol,colinc,minpad,padchars, and the s can be preceded by an at sign ( @ ) modifier. These additional parameters are used to control padding in the output, with at least minpad copies of padchar plus an integer multiple of colinc copies of padchar to make the total width, including the written object, mincol characters wide. The padding is placed on the left if the @ modifier is present, otherwise on the right. mincol and minpad default to 0, colinc defaults to 1, and padchar defaults to space. If specified, padchar is prefixed by a single quote mark. ~~s 指令的一般形式实际上是 ~mincol, colinc, minpad, padchars, s 之前可以有一个 at 符号(@)修饰符。这些附加参数用于控制输出中的填充,以至少 minpad 个 padchar 的拷贝加上 colinc 个 padchar 的拷贝的整数倍来构成总宽度,包括写入对象,宽为 mincol 个字符。如果存在@修饰符,则填充位于左侧,否则位于右侧。mincol 和 minpad 默认为 0,colinc 默认为 1,padchar 默认为空格。指定 padchar 时,它以单引号作为前缀。

(format "~10s" 'hello) \Rightarrow "hello     "
(format "~10@s" 'hello) \Rightarrow "     hello"
(format "~10,,,'*@s" 'hello) \Rightarrow "*****hello"

The ~a directive is similar, but prints the object as with display. ~a 指令类似,但以 display 的方式打印对象。

(format "hi ~s~s" "mom" #\!) \Rightarrow "hi \"mom\"#\\!"
(format "hi ~a~a" "mom" #\!) \Rightarrow "hi mom!"

A tilde may be inserted into the output with , and a newline may be inserted with ~% (or embedded in the string with \n). 可以使用将波浪号插入到输出中,并且可以使用~%插入换行符(或者使用\n 嵌入到字符串中)。

(format "~~line one,~%line two.~~") \Rightarrow "~line one,\nline two.~"
(format "~~line one,\nline two.~~") \Rightarrow "~line one,\nline two.~"

Real numbers may be printed in floating-point notation with ~f. 实数可以用~f 以浮点表示法打印。

(format "~f" 3.14159) \Rightarrow 3.14159

Exact numbers may printed as well as inexact numbers in this manner; they are simply converted to inexact first as if with exact->inexact. 和不精确数一样,精确数也可以以这种方式打印;它们会先转换为不精确数,就如同使用 exact->inexact.

(format "~f" 1/3) \Rightarrow "0.3333333333333333"

The general form is actually ~w,d,k,overflowchar,padcharf. If specified, w determines the overall width of the output, and d the number of digits to the right of the decimal point. padchar, which defaults to space, is the pad character used if padding is needed. Padding is always inserted on the left. The number is scaled by 10k when printed; k defaults to zero. The entire w-character field is filled with copies of overflowchar if overflowchar is specified and the number cannot be printed in w characters. k defaults to 1 If an @ modifier is present, a plus sign is printed before the number for nonnegative inputs; otherwise, a sign is printed only if the number is negative. 一般形式实际上是 ~w,d,k,overflowchar,padcharf. 如果指定,w 确定输出的总宽度,d 确定小数点右边的位数。padchar, 默认为 space,是需要填充时使用的填充字符。填充始终插入到左侧。打印时,数字按 10k 缩放; k 默认为零。如果指定了 overflowchar, 且该数字无法以 w 个字符打印,则整个 w 字符字段将以 overflowchar 的拷贝填充。如果存在@修饰符,则 k 默认为 1, 在输入的非负数字之前打印加号; 否则,仅在数字为负时才打印符号。

(format "~,3f" 3.14159) \Rightarrow "3.142"
(format "~10f" 3.14159) \Rightarrow "   3.14159"
(format "~10,,,'#f" 1e20) \Rightarrow "##########"

Real numbers may also be printed with ~e for scientific notation or with ~g, which uses either floating-point or scientific notation based on the size of the input. 实数也可以使用~e 以科学计数法打印,或使用~g, 根据输入的大小采用浮点表示法或科学计数法。

(format "~e" 1e23) \Rightarrow "1.0e+23"
(format "~g" 1e23) \Rightarrow "1.0e+23"

A real number may also be printed with ~$, which uses monetary notation defaulting to two digits to the right of the decimal point. 实数也可以使用〜$打印,它使用货币符号,默认为小数点右边的两位数。

(format "$~$" (* 39.95 1.06)) \Rightarrow "$42.35"
(format "~$USD" 1/3) \Rightarrow "0.33USD"

Words can be pluralized automatically using p. 可以使用 p 自动把单词变为复数形式。

(format "~s bear~:p in ~s den~:p" 10 1) \Rightarrow "10 bears in 1 den"

Numbers may be printed out in words or roman numerals using variations on ~r. 可以使用~r 的变体以单词或罗马数字打印数字。

(format "~r" 2599) \Rightarrow  "two thousand five hundred ninety-nine"
(format "~:r" 99) \Rightarrow  "ninety-ninth"
(format "~@r" 2599) \Rightarrow "MMDXCIX"

Case conversions can be performed by bracketing a portion of the format string with the @( and ~) directives. 可以通过使用 ~~@(~) 指令括起格式字符串的一部分来执行大小写转换。

(format "~@(~r~)" 2599) \Rightarrow  "Two thousand five hundred ninety-nine"
(format "~@:(~a~)" "Ouch!") \Rightarrow  "OUCH!"

Some of the directives shown above have more options and parameters, and there are other directives as well, including directives for conditionals, iteration, indirection, and justification. Again, please consult a Common Lisp reference for complete documentation. 上面显示的一些指令有更多选项和参数,而且还有其他指令,包括条件,迭代,间接和对齐的指令。同样,请参阅 Common Lisp 参考以获取完整的文档。

An implementation of a greatly simplified version of format appears in Section 12.6 of The Scheme Programming Language, 4th Edition. 在 《Scheme 编程语言》第 4 版的第 12.6 节,有一个极为简化的 format 版本的实现。

procedure: (printf format-string obj ...) procedure: (fprintf textual-output-port format-string obj ...) returns: unspecified libraries: (chezscheme)

These procedures are simple wrappers for format. printf prints the formatted output to the current output, as with a first-argument of #t to format, and fprintf prints the formatted output to the textual-output-port, as when the first argument to format is a port. 这些过程是 format 的简单封装器。printf 将格式化的输出打印到当前输出,就像 format 的第一个参数为 #t 时一样,而 fprintf 将格式化的输出打印到 textual-output-port,就像 format 的第一个参数为端口时一样。

9.14. 输入/输出控制操作

The I/O control operations described in this section are used to control how the reader reads and printer writes, displays, or pretty-prints characters, symbols, gensyms, numbers, vectors, long or deeply nested lists or vectors, and graph-structured objects. 本节中描述的 I/O 控制操作用于控制读取器如何读取,以及打印器如何 write, display, 或 pretty-print 字符,符号,gensyms,数字,向量,长或深度嵌套的列表或向量,和图结构对象。

procedure: (char-name obj) returns: 参见下文 procedure: (char-name name char) returns: unspecified libraries: (chezscheme)

char-name is used to associate names (symbols) with characters or to retrieve the most recently associated name or character for a given character or name. A name can map to only one character, but more than one name can map to the same character. The name most recently associated with a character determines how that character prints, and each name associated with a character may be used after the #\ character prefix to name that character on input. char-name 用于将名称(符号)与字符关联,或者获取给定字符或名称的最近关联的名称或字符。名称只能映射到一个字符,但多个名称可以映射到同一个字符。最近与字符关联的名称决定了字符如何打印,并且每个与字符关联的名称,可以用在#\字符前缀之后,以在输入中命名该字符。

Character associations created by char-name are ignored by the printer unless the parameter print-char-name is set to a true value. The reader recognizes character names established by char-name except after #!r6rs, which is implied within a library or R6RS top-level program. 除非参数对象 print-char-name 设置为真值,否则打印器将忽略 char-name 创建的字符关联。读取器识别由 char-name 创建的字符名称,除了跟在#!r6rs 之后的名称,其隐藏在库或 R6RS 顶层程序中。

In the one-argument form, obj must be a symbol or character. If it is a symbol and a character is associated with the symbol, char-name returns that character. If it is a symbol and no character is associated with the symbol, char-name returns #f. Similarly, if obj is a character, char-name returns the most recently associated symbol for the character or #f if no name is associated with the character. For example, with the default set of character names: 在单参数形式中,obj 必须是符号或字符。如果它是符号并且有字符与此符号相关联,则 char-name 返回该字符。如果它是符号并且没有字符与此符号相关联,则 char-name 返回#f. 类似地,如果 obj 是一个字符,则 char-name 返回该字符最近关联的符号,或者,如果没有与该字符关联的名称,则返回#f. 例如,使用默认的字符名称集:

(char-name #\space) \Rightarrow space
(char-name 'space) \Rightarrow #\space
(char-name 'nochar) \Rightarrow #f
(char-name #\a) \Rightarrow #f

When passed two arguments, name is added to the set of names associated with char, and any other association for name is dropped. char may be #f, in which case any other association for name is dropped and no new association is formed. In either case, any other names associated with char remain associated with char. 传入两个参数时,name 将被添加到与 char 关联的名称集中,并删除 name 的任何其他关联。char 可以是#f, 在这种情况下,名称的任何其他关联都将被删除,并且不会形成新的关联。在任何一种情况下,与 char 相关联的任何其他名称仍与 char 相关联。

The following interactive session demonstrates the use of char-name to establish and remove associations between characters and names, including the association of more than one name with a character. 以下的交互式会话演示了如何使用 char-name 来建立和删除字符和名称之间的关联,包括多个名称与字符的关联。

(print-char-name #t)
(char-name 'etx) \Rightarrow #f
(char-name 'etx #\x3)
(char-name 'etx) \Rightarrow #\etx
(char-name #\x3) \Rightarrow etx
#\etx \Rightarrow #\etx
(eq? #\etx #\x3) \Rightarrow #t
#!r6rs #\etx \Rightarrow exception: invalid character name etx
#!chezscheme #\etx \Rightarrow #\etx
(char-name 'etx #\space)
(char-name #\x3) \Rightarrow #f
(char-name 'etx) \Rightarrow #\etx
#\space \Rightarrow #\etx
(char-name 'etx #f)
#\etx \Rightarrow exception: invalid character name etx
#\space \Rightarrow #\space

(When using the expression editor, it is necessary to type Control-J to force the editor to read the erroneous #\etx input on the two inputs above that result in read errors, since typing Enter causes the expression editor to read the input only if the input is well-formed.) (当使用表达式编辑器时,必须键入 Control-J 以强制编辑器读取上面两个输入上的错误#\etx 输入,其会导致读取错误,因为键入 Enter 会导致表达式编辑器仅在输入形式合法时读取输入。)

The reader does not recognize hex scalar value escapes in character names, as it does in symbols, so #\new\x6c;ine is not equivalent to #\newline. In general, programmers should avoid the use of character name symbols that cannot be entered without the use of hex scalar value escapes or other symbol-name escape mechanisms, since such character names will not be readable. 读取器不会像对符号一样识别字符名称中转义的十六进制标量值,因此#\new\x6c;ine 不等同于#\newline. 通常,程序员应避免使用那些不使用十六进制标量值转义或其他符号名转义机制则无法输入的字符名称符号,因为这类字符名称将无法读取。

thread parameter: print-char-name libraries: (chezscheme)

When print-char-name is set to #f (the default), associations created by char-name are ignored by write, put-datum, pretty-print, and the format "s" directive. Otherwise, these procedures use the names established by char-name when printing character objects. 当 print-char-name 设置为#f(默认值)时,char-name 创建的关联将被 write,put-datum,pretty-print 和 ~format "~s" 指令忽略。否则,这些过程在打印字符对象时使用 char-name 建立的名称。

(char-name 'etx #\x3)
(format "~s" #\x3) \Rightarrow "#\\x3"
(parameterize ([print-char-name #t])
  (format "~s" #\x3)) \Rightarrow "#\\etx"

thread parameter: case-sensitive libraries: (chezscheme)

The case-sensitive parameter determines whether the reader is case-sensitive with respect to symbol and character names. When set to true (the default, as required by the Revised6 Report) the case of alphabetic characters within symbol names is significant. When set to #f, case is insignificant. More precisely, when set to #f, symbol and character names are folded (as if by string-foldcase); otherwise, they are left as they appear in the input. 参数对象 case-sensitive 决定读取器是否对符号和字符名称区分大小写。当设置为真(默认值,根据 R6RS 的要求)时,符号名称中字母字符的大小写是有意义的。当设置为#f 时,则大小写无关紧要。更准确地说,当设置为#f 时,符号和字符名称被折叠(就像使用 string-foldcase 一样); 否则,它们会以它们出现的形式保留在输入中。

The value of the case-sensitive matters only if neither #!fold-case nor #!no-fold-case has appeared previously in the same input stream. That is, symbol and character name are folded if #!fold-case has been seen. They are not folded if #!no-fold-case has been seen. If neither has been seen, they are folded if and only if (case-sensitive) is #f. 只有当#!fold-case 和#!no-fold-case 都没有先出现在同一输入流中时,case-sensitive 的值才有作用。也就是说,如果遇到#!fold-case,则折叠符号和字符名。如果遇到#!no-fold-case,则它们不会被折叠。如果两者都没有遇到,那么当且仅当 (case-sensitive) 为 #f 时它们被折叠。

(case-sensitive) \Rightarrow #t
(eq? 'abc 'ABC) \Rightarrow #f
'ABC \Rightarrow ABC
(case-sensitive #f)
'ABC \Rightarrow abc
(eq? 'abc 'ABC) \Rightarrow #t

thread parameter: print-graph libraries: (chezscheme)

When print-graph is set to a true value, write and pretty-print locate and print objects with shared structure, including cycles, in a notation that may be read subsequently with read. This notation employs the syntax "#n=obj," where n is a nonnegative integer and obj is the printed representation of an object, to label the first occurrence of obj in the output. The syntax "#n#" is used to refer to the object labeled by n thereafter in the output. print-graph is set to #f by default. 当 print-graph 设置为真值时,write 和 pretty-print 定位并打印具有共享结构的对象,包括环,以一种可以随后用 read 读取的表示法。这种表示法使用语法 "#n=obj", 其中 n 是非负整数,obj 是对象的打印表示形式,用于标记输出中第一次出现的 obj. 语法"#n#"用于引用后面输出中以 n 标记的对象。print-graph 默认设置为#f。

If graph printing is not enabled, the settings of print-length and print-level are insufficient to force finite output, and write or pretty-print detects a cycle in an object it is given to print, a warning is issued (an exception with condition type &warning is raised) and the object is printed as if print-graph were enabled. 如果没有启用图打印,则 print-length 和 print-level 的设置不足以强制有限输出,而当 write 或 pretty-print 检测到要打印的对象中有环时,则会发出警告(抛出条件类型的 &warning 异常)并打印对象,如同 print-graph 启用时一样。

Since objects printed through the ~s option in the format control strings of format, printf, and fprintf are printed as with write, the printing of such objects is also affected by print-graph. 由于通过 format,printf 和 fprintf 的格式控制字符串中的~s 选项打印的对象和使用 write 打印的一样,因此 print-graph 也会影响此类对象的打印。

(parameterize ([print-graph #t])
  (let ([x (list 'a 'b)])
    (format "~s" (list x x)))) \Rightarrow "(#0=(a b) #0#)"

(parameterize ([print-graph #t])
  (let ([x (list 'a 'b)])
    (set-car! x x)
    (set-cdr! x x)
    (format "~s" x))) \Rightarrow "#0=(#0# . #0#)"

The graph syntax is understood by the procedure read, allowing graph structures to be printed and read consistently. 过程 read 可以理解图语法,从而可以一致地打印和读取图结构。

thread parameter: print-level thread parameter: print-length libraries: (chezscheme)

These parameters can be used to limit the extent to which nested or multiple-element structures are printed. When called without arguments, print-level returns the current print level and print-length returns the current print length. When called with one argument, which must be a nonnegative fixnum or #f, print-level sets the current print level and print-length sets the current print length to the argument. 这些参数对象可用于限制嵌套或多元素结构的打印范围。当不带参数调用时,print-level 返回当前打印级别,print-length 返回当前打印长度。当使用一个参数(必须是非负的 fixnum 或#f)调用时,print-level 把当前的打印级别设置为实参,print-length 将把当前的打印长度设置为实参。

When print-level is set to a nonnegative integer n, write and pretty-print traverse only n levels deep into nested structures. If a structure being printed exceeds n levels of nesting, the substructure beyond that point is replaced in the output by an ellipsis ( … ). print-level is set to #f by default, which places no limit on the number of levels printed. 当 print-level 被设置为非负整数 n 时,write 和 pretty-print 仅在嵌套结构中遍历到第 n 层。如果当前打印的结构超过 n 层嵌套,则超出该层的子结构将在输出中被省略号(…)替换。print-level 默认设置为#f,即对打印的层数没有限制。

When print-length is set to a nonnegative integer n, the procedures write and pretty-print print only n elements of a list or vector, replacing the remainder of the list or vector with an ellipsis ( … ). print-length is set to #f by default, which places no limit on the number of elements printed. 当 print-length 被设置为非负整数 n 时,过程 write 和 pretty-print 仅打印列表或向量中的 n 个元素,用省略号(…)替换列表或向量的其余部分。print-length 默认设置为#f,即对打印的元素数量没有限制。

Since objects printed through the ~s option in the format control strings of format, printf, and fprintf are printed as with write, the printing of such objects is also affected by print-level and print-length. 由于通过 format,printf 和 fprintf 的格式控制字符串中的~s 选项打印的对象和使用 write 打印的一样,因此打印此类对象也会受到 print-level 和 print-length 的影响。

The parameters print-level and print-length are useful for controlling the volume of output in contexts where only a small portion of the output is needed to identify the object being printed. They are also useful in situations where circular structures may be printed (see also print-graph). 在只需要输出的一小部分来识别打印对象的环境中,参数对象 print-level 和 print-length 对于控制输出量是很有用的。它们在可能打印环结构的情况下也很有用(另请参见 print-graph)。

(format "~s" '((((a) b) c) d e f g)) \Rightarrow "((((a) b) c) d e f g)"

(parameterize ([print-level 2])
  (format "~s" '((((a) b) c) d e f g))) \Rightarrow "(((...) c) d e f g)"

(parameterize ([print-length 3])
  (format "~s" '((((a) b) c) d e f g))) \Rightarrow "((((a) b) c) d e ...)"

(parameterize ([print-level 2]
               [print-length 3])
  (format "~s" '((((a) b) c) d e f g))) \Rightarrow "(((...) c) d e ...)"

thread parameter: print-radix libraries: (chezscheme)

The print-radix parameter determines the radix in which numbers are printed by write, pretty-print, and display. Its value should be an integer between 2 and 36, inclusive. Its default value is 10. print-radix 参数对象决定通过 write, pretty-print, 和 display 打印数字时的基数。它的值应该是 2 到 36 之间的整数,包括 2 和 36。其默认值为 10。

When the value of print-radix is not 10, write and pretty-print print a radix prefix before the number (#b for radix 2, #o for radix 8, #x for radix 16, and #nr for any other radix n). 当 print-radix 的值不是 10 时,write 和 pretty-print 会在数字前打印一个基数前缀(基数 2 为#b,基数 8 为#o,基数 16 为 #x,任何其他基数 n 为#nr)。

Since objects printed through the ~s and ~a options in the format control strings of format, printf, and fprintf are printed as with write and display, the printing of such objects is also affected by print-radix. 由于通过 format,printf 和 fprintf 的格式控制字符串中的~s 和~a 选项打印的对象与使用 write 和 display 打印的一样,因此这类对象的打印也会受到 print-radix 影响。

(format "~s" 11242957) \Rightarrow "11242957"

(parameterize ([print-radix 16])
  (format "~s" 11242957)) \Rightarrow "#xAB8DCD"

(parameterize ([print-radix 16])
  (format "~a" 11242957)) \Rightarrow "AB8DCD"

thread parameter: print-gensym libraries: (chezscheme)

When print-gensym is set to #t (the default) gensyms are printed with an extended symbol syntax that includes both the pretty name and the unique name of the gensym: #{pretty-name unique-name}. When set to pretty, the pretty name only is shown, with the prefix #:. When set to pretty/suffix, the printer prints the gensym's "pretty" name along with a suffix based on the gensym's "unique" name, separated by a dot ( "." ). If the gensym's unique name is generated automatically during the current session, the suffix is that portion of the unique name that is not common to all gensyms created during the current session. Otherwise, the suffix is the entire unique name. When set to #f, the pretty name only is shown, with no prefix. 当 print-gensym 设置为#t(默认值)时,gensyms 将使用扩展符号语法打印,该语法包括美观名称和 gensym 的唯一名称:#{pretty-name unique-name}. 当设置为 pretty 时,仅显示美观名称,带有前缀#:. 当设置为 pretty/suffix 时,打印器打印 gensym 的“美观”名称以及基于 gensym 的“唯一”名称生成的后缀,以点(".")分隔。如果 gensym 的唯一名称是在当前会话期间自动生成的,则后缀是唯一名称中与当前会话期间创建的所有 gensyms 不同的部分。否则,后缀为完整的唯一名称。设置为#f 时,仅显示不带前缀的美观名称。

Since objects printed through the ~s option in the format control strings of format, printf, errorf, etc., are printed as with write, the printing of such objects is also affected by print-gensym. 由于通过 format,printf 和 errorf 等格式控制字符串中的~s 选项打印的对象与使用 write 打印的一样,因此这类对象的打印也会受到 print-gensym 影响。

When printing an object that may contain more than one occurrence of a gensym and print-graph is set to pretty or #f, it is useful to set print-graph to #t so that multiple occurrences of the same gensym are marked as identical in the output. 当打印一个可能包含同一 gensym 的多次出现的对象,而 print-graph 被设置为 pretty 或#f 时,将 print-graph 设置为#t 非常有用,这样多次出现的同一 gensym 会在输出中被标记为相同的。

(let ([g (gensym)])
  (format "~s" g)) \Rightarrow "#{g0 bdids2xl6v49vgwe-a}"

(let ([g (gensym)])
  (parameterize ([print-gensym 'pretty])
    (format "~s" g))) \Rightarrow "#:g1

(let ([g (gensym)])
  (parameterize ([print-gensym #f])
    (format "~s" g))) \Rightarrow "g2"

(let ([g (gensym)])
  (parameterize ([print-graph #t] [print-gensym 'pretty])
    (format "~s" (list g g)))) \Rightarrow "(#0=#:g3 #0#)"

(let ([g1 (gensym "x")]
      [g2 (gensym "x")]
      [g3 (gensym "y")])
  (parameterize ([print-gensym 'pretty/suffix])
    (format "~s ~s ~s" g1 g2 g3))) \Rightarrow "x.1 x.2 y.3"

thread parameter: print-brackets libraries: (chezscheme)

When print-brackets is set to a true value, the pretty printer (see pretty-print) uses square brackets rather than parentheses around certain subexpressions of common control structures, e.g., around let bindings and cond clauses. print-brackets is set to #t by default. 当 print-bracket 设置为真值时,美观打印器(参见 pretty-print)使用方括号而不是圆括号来括起共有控制结构的特定子表达式,例如,let 绑定和 cond 子句。print-bracket 默认设置为#t。

(let ([p (open-output-string)])
  (pretty-print '(let ([x 3]) x) p) \Rightarrow "(let ([x 3]) x)
  (get-output-string p))             "

(parameterize ([print-brackets #f])
  (let ([p (open-output-string)])
    (pretty-print '(let ([x 3]) x) p) \Rightarrow "(let ((x 3)) x)
    (get-output-string p)))            "

thread parameter: print-extended-identifiers libraries: (chezscheme)

Chez Scheme extends the syntax of identifiers as described in Section 1.1, except within a set of forms prefixed by #!r6rs (which is implied by in a library or top-level program). Chez Scheme 扩展了 1.1 节中描述的标识符的语法,除了在一组以#!r6rs 为前缀的形式中(暗含在库或顶层程序中)。

When this parameter is set to false (the default), identifiers in the extended set are printed with hex scalar value escapes as necessary to conform to the R6RS syntax for identifiers. When this parameter is set to a true value, identifiers in the extended set are printed without the escapes. Identifiers whose names fall outside of both syntaxes are printed with the escapes regardless of the setting of this parameter. 当此参数对象设置为假(默认值)时,打印扩展集中的标识符将根据需要使用十六进制标量值转义,以符合标识符的 R6RS 语法。当此参数对象设置为真值时,打印扩展集中的标识符将不进行转义。无论此参数对象设置为何值,名称超出两种语法范围的标识符打印时都将使用转义。

例如:

(parameterize ([print-extended-identifiers #f])
  (printf "~s\n~s\n"
    '(1+ --- { } .xyz)
    (string->symbol "123")))

打印

(\x31;+ \x2D;-- \x7B; \x7D; \x2E;xyz)
\x31;23

(parameterize ([print-extended-identifiers #t])
  (printf "~s\n~s\n"
    '(1+ --- { } .xyz)
    (string->symbol "123")))

打印

(1+ --- { } .xyz)
\x31;23

thread parameter: print-vector-length libraries: (chezscheme)

When print-vector-length is set to a true value, write, put-datum, and pretty-print includes the length for all vectors between the "#" and open parenthesis, all bytevectors between the "#vu8" and open parenthesis, and all fxvectors between the "#vfx" and open parenthesis. This parameter is set to #f by default. 当 print-vector-length 设置为真值时,write,put-datum 和 pretty-print 包含所有向量在"#"和左括号之间的长度,所有字节向量在"#vu8"和左括号之间的长度, 以及所有 fxvector(定长数向量)在"#vfx"和左括号之间的长度。默认情况下,此参数设置为#f.

When print-vector-length is set to a true value, write, put-datum, and pretty-print also suppress duplicated trailing elements in the vector to reduce the amount of output. This form is also recognized by the reader. 当 print-vector-length 设置为真值时,write,put-datum 和 pretty-print 同时会省略向量尾部的重复元素,以减少输出量。这种形式也可以被读取器识别。

Since objects printed through the ~s option in the format control strings of format, printf, and fprintf are printed as with write, the printing of such objects is also affected by the setting of print-vector-length. 由于通过 format,printf 和 fprintf 的格式控制字符串中的~s 选项打印的对象与使用 write 打印时一样,因此这类对象的打印也会受到 print-vector-length 的设置影响。

(format "~s" (vector 'a 'b 'c 'c 'c)) \Rightarrow "#(a b c c c)"

(parameterize ([print-vector-length #t])
  (format "~s" (vector 'a 'b 'c 'c 'c))) \Rightarrow "#5(a b c)"

(parameterize ([print-vector-length #t])
  (format "~s" (bytevector 1 2 3 4 4 4))) \Rightarrow "#6vu8(1 2 3 4)"

(parameterize ([print-vector-length #t])
  (format "~s" (fxvector 1 2 3 4 4 4))) \Rightarrow "#6vfx(1 2 3 4)"

thread parameter: print-precision libraries: (chezscheme)

When print-precision is set to #f (the default), write, put-datum, pretty-print, and the format "~s" directive do not include the vertical-bar "mantissa-width" syntax after each floating-point number. When set to a nonnegative exact integer, the mantissa width is included, as per the precision argument to number->string. 当 print-precision 设置为#f(默认值)时,write,put-datum,pretty-print 和 format 的 “~s”指令在每个浮点数后不包括竖杠“小数宽度”语法。当设置为非负精确整数时,将包括小数宽度,根据 number->string 的 precision 参数。

thread parameter: print-unicode libraries: (chezscheme)

When print-unicode is set to #f, write, put-datum, pretty-print, and the format "~s" directive display Unicode characters with encodings 8016 (128) and above that appear within character objects, symbols, and strings using hexadecimal character escapes. When set to a true value (the default), they are displayed like other printing characters, as if by put-char. 当 print-unicode 设置为#f 时,write,put-datum,pretty-print 和 format 的 "~s"指令显示编码 8016(128)及以上的 Unicode 字符,这些字符使用十六进制字符转义出现在字符对象,符号和字符串中。设置为真值(默认值)时,它们会像其他打印字符一样显示,就像使用 put-char 一样。

(format "~s" #\x3bb) \Rightarrow "#\\\lambda"
(parameterize ([print-unicode #f])
  (format "~s" #\x3bb)) \Rightarrow "#\\x3BB"

9.15. 快速加载(Fasl)输出

The procedures write and pretty-print print objects in a human readable format. For objects with external datum representations, the output produced by write and pretty-print is also machine-readable with read. Objects with external datum representations include pairs, symbols, vectors, strings, numbers, characters, booleans, and records but not procedures and ports. 过程 write 和 pretty-print 以人类可读的格式打印对象。对于具有外部数据表示形式的对象,write 和 pretty-print 产生的输出同样也可以通过 read 被机器读取。具有外部数据表示形式的对象包括点对,符号,向量,字符串,数字,字符,布尔值和记录,但不包括过程和端口。

An alternative fast loading, or fasl, format may be used for objects with external datum representations. The fasl format is not human readable, but it is machine readable and both more compact and more quickly processed by read. This format is always used for compiled code generated by compile-file, but it may also be used for data that needs to be written and read quickly, such as small databases encoded with Scheme data structures. 一种可选的快速加载(或称 fasl )格式,可以用于具有外部数据表示形式的对象。fasl 格式不是人类可读的,而是机器可读的,并且更加紧凑,且可以被 read 更快地处理。此格式总是用于 compile-file 生成的编译代码,但也可用于需要快速写入和读取的数据,例如使用 Scheme 数据结构编码的小型数据库。

Objects are printed in fasl format with fasl-write. Because the fasl format is a binary format, fasl output must be written to a binary port. For this reason, it is not possible to include data written in fasl format with textual data in the same file, as was the case in earlier versions of Chez Scheme. Similarly, the (textual) reader does not handle objects written in fasl format; the procedure fasl-read, which requires a binary input port, must be used instead. 对象使用 fasl-write 打印为 fasl 格式。由于 fasl 格式是二进制格式,因此必须将 fasl 输出写入到二进制端口。出于这个原因,不可能将以 fasl 格式写入的数据与文本数据包含在同一文件中,就像在早期版本的 Chez Scheme 中一样。类似地,(文本)读取器不处理以 fasl 格式写入的对象;必须转而使用需要二进制输入端口的过程 fasl-read.

procedure: (fasl-write obj binary-output-port) procedure: (fasl-read binary-input-port) returns: unspecified libraries: (chezscheme)

fasl-write writes the fasl representation of obj to binary-output-port. An exception is raised with condition-type &assertion if obj or any portion of obj has no external fasl representation, e.g., if obj is or contains a procedure. fasl-write 将 obj 的 fasl 表示形式写入 binary-output-port. 如果 obj 或 obj 的任何部分没有外部 fasl 表示形式,例如,如果 obj 是一个过程或包含过程,则会抛出条件类型的 &assertion 异常。

fasl-read reads one object from binary-input-port, which must be positioned at the front of an object written in fasl format. fasl-read returns the eof object if the file is positioned at the end of file. fasl-read 从 binary-input-port 读取一个对象,该端口必须位于以 fasl 格式写入的对象的前面。如果文件位于文件末尾,则 fasl-read 返回 eof 对象。

(define bop (open-file-output-port "tmp.fsl"))
(fasl-write '(a b c) bop)
(close-port bop)

(define bip (open-file-input-port "tmp.fsl"))
(fasl-read bip) \Rightarrow (a b c)
(fasl-read bip) \Rightarrow #!eof
(close-port bip)

procedure: (fasl-file ifn ofn) returns: unspecified libraries: (chezscheme)

ifn and ofn must be strings. fasl-file may be used to convert a file in human-readable format into an equivalent file written in fasl format. fasl-file reads each object in turn from the file named by ifn and writes the fasl format for the object onto the file named by ofn. If the file named by ofn already exists, it is replaced. ifn 和 ofn 必须是字符串。fasl-file 可用于将人类可读格式的文件转换为以 fasl 格式写入的等价文件。fasl-file 从 ifn 命名的文件中依次读取每个对象,并将该对象的 fasl 格式写入到由 ofn 命名的文件中。如果 ofn 命名的文件已存在,则替换它。

9.16. 文件系统接口

This section describes operations on files, directories, and pathnames. 本节介绍有关文件,目录和路径名的操作。

global parameter: current-directory global parameter: cd libraries: (chezscheme)

When invoked without arguments, current-directory returns a string representing the current working directory. Otherwise, the current working directory is changed to the directory specified by the argument, which must be a string representing a valid directory pathname. 当不带参数调用时,current-directory 返回表示当前工作目录的字符串。否则,当前工作目录将更改为参数指定的目录,该参数必须是表示有效目录路径名的字符串。

cd is bound to the same parameter. cd 被绑定为同一个参数对象。

procedure: (directory-list path) 返回:文件名列表 libraries: (chezscheme)

path must be a string. The return value is a list of strings representing the names of files found in the directory named by path. directory-list raises an exception with condition type &i/o-filename if path does not name a directory or if the process cannot list the directory. path 必须是一个字符串。返回值是一个字符串列表,表示在 path 指定的目录中找到的文件的名称。如果 path 没有指向一个目录或进程无法列出目录,则 directory-list 会抛出条件类型的&i/o-filename 异常。

procedure: (file-exists? path) procedure: (file-exists? path follow?) 返回: 如果 path 指定的文件存在,则为 #t, 否则为 #f. libraries: (chezscheme)

path must be a string. If the optional follow? argument is true (the default), file-exists? follows symbolic links; otherwise it does not. Thus, file-exists? will return #f when handed the pathname of a broken symbolic link unless follow? is provided and is #f. path 必须是一个字符串。如果可选的 follow? 参数为真(默认值),则 file-exists? 会跟随符号链接;否则它不会。因此,当传入的路径名为损坏的符号链接时,file-exists? 会返回 #f, 除非提供参数 follow?, 且其值为 #f.

The Revised6 Report file-exists? does not accept the optional follow? argument. Whether it follows symbolic links is unspecified. R6RS 中的 file-exists? 不接受可选的 follow? 参数。它是否跟随符号链接是未指定的。

procedure: (file-regular? path) procedure: (file-regular? path follow?) 返回: 如果 path 指定的文件是一个常规文件,则为 #t, 否则为 #f. libraries: (chezscheme)

path must be a string. If the optional follow? argument is true (the default), file-regular? follows symbolic links; otherwise it does not. path 必须是一个字符串。如果可选的 follow? 参数为真(默认值),则 file-regular? 会跟随符号链接;否则它不会。

procedure: (file-directory? path) procedure: (file-directory? path follow?) 返回: 如果 path 指定的文件是一个目录,则为 #t, 否则为 #f. libraries: (chezscheme)

path must be a string. If the optional follow? argument is true (the default), this procedure follows symbolic links; otherwise it does not. path 必须是一个字符串。如果可选的 follow? 参数为真(默认值),则此过程会跟随符号链接;否则它不会。

procedure: (file-symbolic-link? path) 返回: 如果 path 指定的文件是一个符号链接,则为 #t, 否则为 #f. libraries: (chezscheme)

path must be a string. file-symbolic-link? never follows symbolic links in making its determination. path 必须是一个字符串。file-symbolic-link? 在做出判定时永远不会跟随符号链接。

procedure: (file-access-time path/port) procedure: (file-access-time path/port follow?) 返回:指定文件的访问时间 procedure: (file-change-time path/port) procedure: (file-change-time path/port follow?) 返回:指定文件的更改时间 procedure: (file-modification-time path/port) procedure: (file-modification-time path/port follow?) 返回:指定文件的修改时间 libraries: (chezscheme)

path/port must be a string or port. If path/port is a string, the time returned is for the file named by the string, and the optional follow? argument determines whether symbolic links are followed. If follow? is true (the default), this procedure follows symbolic links; otherwise it does not. If path/port is a port, it must be a file port, and the time returned is for the associated file. In this case, follow? is ignored. path/port 必须是字符串或端口。如果 path/port 是一个字符串,则返回的时间是关于由字符串命名的文件,可选的 follow? 参数决定是否跟随符号链接。如果 follow? 为真(默认值),则此过程会跟随符号链接;否则它不会。如果 path/port 是端口,则它必须是文件端口,并且返回的时间是关于关联的文件。在这种情况下,会忽略参数 follow?.

The returned times are represented as time objects (Section 12.10). 返回的时间表示为时间对象(第 12.10 节)。

procedure: (mkdir path) procedure: (mkdir path mode) returns: unspecified libraries: (chezscheme)

path must be a string. mode must be a fixnum. path 必须是一个字符串。mode 必须是 fixnum.

mkdir creates a directory with the name given by path. All path path components leading up to the last must already exist. If the optional mode argument is present, it overrides the default permissions for the new directory. Under Windows, the mode argument is ignored. mkdir 创建一个名称由 path 给出的目录。path 的所有路径组件从头到尾都必须已存在。如果提供了可选的 mode 参数,则它将覆盖新目录的默认权限。在 Windows 下,mode 参数将被忽略。

mkdir raises an exception with condition type &i/o-filename if the directory cannot be created. 如果目录无法创建,则 mkdir 会抛出条件类型的&i/o-filename 异常。

procedure: (delete-file path) procedure: (delete-file path error?) returns: 参见下文 libraries: (chezscheme)

path must be a string. delete-file removes the file named by path. If the optional error? argument is #f (the default), delete-file returns a boolean value: #t if the operation is successful and #f if it is not. Otherwise, delete-file returns an unspecified value if the operation is successful and raises an exception with condition type &i/o-filename if it is not. path 必须是一个字符串。delete-file 删除 path 命名的文件。如果可选的 error? 参数为 #f(默认值),则 delete-file 返回一个布尔值:如果操作成功则返回#t,如果不成功则返回#f. 否则,如果操作成功,则 delete-file 返回一个未定义的值,如果不成功,则抛出条件类型的&i/o-filename 异常。

The Revised6 Report delete-file does not accept the optional error? argument but behaves as if error? is true. R6RS 中的 delete-file 不接受可选的 error? 参数,但其行为如同 error? 为真。

procedure: (delete-directory path) procedure: (delete-directory path error?) returns: 参见下文 libraries: (chezscheme)

path must be a string. delete-directory removes the directory named by path. If the optional error? argument is #f (the default), delete-directory returns a boolean value: #t if the operation is successful and #f if it is not. Otherwise, delete-directory returns an unspecified value if the operation is successful and raises an exception with condition type &i/o-filename if it is not. The behavior is unspecified if the directory is not empty, but on most systems the operations will not succeed. path 必须是一个字符串。delete-directory 删除 path 命名的目录。如果可选的 error? 参数为 #f(默认值),delete-directory 返回一个布尔值:如果操作成功则返回#t, 如果不成功则返回#f. 否则,如果操作成功,则 delete-directory 返回一个未定义的值,如果不成功,则抛出条件类型的&i/o-filename 异常。如果目录不为空,则行为是未定义的,但在大多数系统上,操作将不会成功。

procedure: (rename-file old-pathname new-pathname) returns: unspecified libraries: (chezscheme)

old-pathname and new-pathname must be strings. rename-file changes the name of the file named by old-pathname to new-pathname. If the file does not exist or cannot be renamed, an exception is raised with condition type &i/o-filename. old-pathname 和 new-pathname 必须是字符串。rename-file 将 old-pathname 命名的文件名更改为 new-pathname. 如果文件不存在或无法重命名,则会抛出条件类型的&i/o-filename 异常。

procedure: (chmod path mode) returns: unspecified libraries: (chezscheme)

path must be a string. mode must be a fixnum. path 必须是一个字符串。mode 必须是 fixnum.

chmod sets the permissions on the file named by path to mode. Bits 0, 1, and 2 of mode are the execute, write, and read permission bits for users other than the file's owner who are not in the file's group. Bits 3-5 are the execute, write, and read permission bits for users other than the file's owner but in the file's group. Bits 6-8 are the execute, write, and read permission bits for the file's owner. Bits 7-9 are the Unix sticky, set-group-id, and set-user-id bits. Under Windows, all but the user "write" bit are ignored. If the file does not exist or the permissions cannot be changed, an exception is raised with condition type &i/o-filename. chmod 将 path 命名的文件权限设置为 mode. mode 的第 0,1 和 2 位是执行,写入和读取权限位,针对文件所有者以外,不在文件组中的用户。第 3-5 位是除文件所有者之外但在文件组中的用户的执行,写入和读取权限位。第 6-8 位是文件所有者的执行,写入和读取权限位。第 7-9 位是 Unix sticky,set-group-id 和 set-user-id 位。在 Windows 下,除了用户“写入”位之外的所有位都被忽略。如果文件不存在或无法更改权限,则会抛出条件类型的&i/o-filename 异常。

procedure: (get-mode path) procedure: (get-mode path follow?) 返回:path 的当前权限模式 libraries: (chezscheme)

path must be a string. get-mode retrieves the permissions on the file named by path and returns them as a fixnum in the same form as the mode argument to chmod. If the optional follow? argument is true (the default), this procedure follows symbolic links; otherwise it does not. path 必须是一个字符串。get-mode 获取由 path 命名的文件的权限,并以与 chmod 的 mode 参数相同的形式将它们作为 fixnum 返回。如果可选的 follow? 参数为真(默认值),则此过程会跟随符号链接;否则它不会。

procedure: (directory-separator? char) 返回: 如果 char 是文件分隔符,则为 #t, 否则为 #f. libraries: (chezscheme)

The character #\/ is a directory separator on all current machine types, and #\\ is a directory separator under Windows. 字符#\/是所有当前计算机类型上的目录分隔符,#\\是 Windows 下的目录分隔符。

procedure: (directory-separator) 返回:首选目录分隔符 libraries: (chezscheme)

The preferred directory separator is #\\ for Windows and #\/ for other systems. Windows 上的首选目录分隔符是#\\, 其它系统上是 #\/.

procedure: (path-first path) procedure: (path-rest path) procedure: (path-last path) procedure: (path-parent path) procedure: (path-extension path) procedure: (path-root path) returns: path 的指定组件 procedure: (path-absolute? path) 返回: 如果 path 是绝对路径,则为 #t, 否则为 #f. libraries: (chezscheme)

path must be a string. The return value is also a (possibly empty) string. path 必须是一个字符串。返回值也是(可能为空)字符串。

The path first component is the first directory in the path, or the empty string if the path consists only of a single filename. The path rest component is the portion of the path that does not include the path first component or the directory separator (if any) that separates it from the rest of the path. The path last component is the last (filename) portion of path. The path parent component is the portion of path that does not include the path last component, if any, or the directory separator that separates it from the rest of the path. 路径的第一个组件是路径中的第一个目录,如果路径仅包含单个文件名,则为空字符串。路径的剩余组件是路径中不包含路径第一个组件或将它与路径其余部分分隔开的目录分隔符(如果有)的部分。路径最后一个组件是路径的最后一个部分(文件名)。路径父组件是路径中不包含路径最后一个组件的部分(如果有),或者是将其与路径的其余部分分隔开的目录分隔符。

If the first component of the path names a root directory (including drives and shares under Windows), home directory (e.g., ~/abc or ~user/abc), the current directory (.), or the parent directory (..), path-first returns that component. For paths that consist only of such a directory, both path-first and path-parent act as identity procedures, while path-rest and path-last return the empty string. 如果路径的第一个组件指向一个根目录(包括 Windows 下的驱动器和共享文件夹),主目录(例如,~/abc 或~user/abc),当前目录(.)或父目录(..) ,path-first 返回该组件。对于仅包含这样一个目录的路径,path-first 和 path-parent 表现为同样的过程,而 path-rest 和 path-last 返回空字符串。

The path extension component is the portion of path that follows the last dot (period) in the last component of a path name. The path root component is the portion of path that does not include the extension, if any, or the dot that precedes it. 路径扩展组件是路径名称的最后一个组件中最后一个点号(句点)之后的路径部分。路径根组件是路径中不包含扩展名的部分(如果有)或其前面的点。

If the first component names a root directory (including drives and shares under Windows) or home directory, path-absolute? returns #t. Otherwise, path-absolute? returns #f. 如果第一个组件指向一个根目录(包括 Windows 下的驱动器和共享文件夹)或主目录,path-absolute? 返回#t. 否则,path-absolute? 返回#f.

下表标识了几个示例路径的组件,下划线表示空字符串。

path abs first rest parent last root ext
a #f \_ a \_ a a \_
a/ #f a \_ a \_ a/ \_
a/b #f a b a b a/b \_
a/b.c #f a b.c a b.c a/b c
/a/b.c #t / a/b.c /a b.c /a/b c
~/a/b.c #t ~ a/b.c ~/a b.c ~/a/b c
~u/a/b.c #t ~u a/b.c ~u/a b.c ~u/a/b c
../.. #f .. .. .. .. ../.. \_

The second table shows the components when Windows drives and shares are involved. 第二个表显示涉及 Windows 驱动器和共享文件夹时的组件。

path abs first rest parent last root ext
c: #f c: \_ c: \_ c: \_
c:/ #t c:/ \_ c:/ \_ c:/ \_
c:a/b #f c: a/b c:a b c:a/b \_
//s/a/b.c #t //s a/b.c //s/a b.c //s/a/b c
//s.com #t //s.com \_ //s.com \_ //s.com \_

The following procedure can be used to reproduce the tables above. 以下过程可用于重新生成上表。

(define print-table
  (lambda path*
    (define print-row
      (lambda (abs? path first rest parent last root extension)
        (printf "~a~11t~a~17t~a~28t~a~39t~a~50t~a~61t~a~73t~a\n"
          abs? path first rest parent last root extension)))
    (print-row "path" "abs" "first" "rest" "parent" "last" "root" "ext")
    (for-each
      (lambda (path)
        (define uscore (lambda (s) (if (eqv? s "") "_" s)))
        (apply print-row path
          (map (lambda (s) (if (eqv? s "") "_" s))
               (list (path-absolute? path) (path-first path)
                 (path-rest path) (path-parent path) (path-last path)
                 (path-root path) (path-extension path)))))
      path*)))

For example, the first table can be produced with: 例如,可以使用以下代码生成第一个表格:

(print-table "a" "a/" "a/b" "a/b.c" "/" "/a/b.c" "~/a/b.c"
  "~u/a/b.c" "../..")

while the second can be produced (under Windows) with: 而第二个表格可以使用以下代码生成(在 Windows 下):

(print-table "c:" "c:/" "c:a/b" "//s/a/b.c" "//s.com")

9.17. 通用端口示例

This section presents the definitions for three types of generic ports: two-way ports, transcript ports, and process ports. 本节介绍三种通用端口的定义:双向端口,转录端口和进程端口。

Two-way ports. The first example defines make-two-way-port, which constructs a textual input/output port from a given pair of textual input and output ports. For example: 双向端口。第一个示例定义了 make-two-way-port, 它从给定的一对文本输入和文本输出端口构造文本输入/输出端口。例如:

(define ip (open-input-string "this is the input"))
(define op (open-output-string))
(define p (make-two-way-port ip op))

The port returned by make-two-way-port is both an input and an output port, and it is also a textual port: make-two-way-port 返回的端口既是输入端口又是输出端口,它同时也是一个文本端口:

(port? p) \Rightarrow #t
(input-port? p) \Rightarrow #t
(output-port? p) \Rightarrow #t
(textual-port? p) \Rightarrow #t

Items read from a two-way port come from the constituent input port, and items written to a two-way port go to the constituent output port: 从双向端口读取的项目来自输入端口部分,写入双向端口的项目发送到输出端口部分:

(read p) \Rightarrow this
(write 'hello p)
(get-output-string op) \Rightarrow hello

The definition of make-two-way-port is straightforward. To keep the example simple, no local buffering is performed, although it would be more efficient to do so. make-two-way-port 的定义很直白。为了使示例保持简单,不执行本地缓冲,尽管这样做会更有效。

(define make-two-way-port
  (lambda (ip op)
    (define handler
      (lambda (msg . args)
        (record-case (cons msg args)
          [block-read (p s n) (block-read ip s n)]
          [block-write (p s n) (block-write op s n)]
          [char-ready? (p) (char-ready? ip)]
          [clear-input-port (p) (clear-input-port ip)]
          [clear-output-port (p) (clear-output-port op)]
          [close-port (p) (mark-port-closed! p)]
          [flush-output-port (p) (flush-output-port op)]
          [file-position (p . pos) (apply file-position ip pos)]
          [file-length (p) (file-length ip)]
          [peek-char (p) (peek-char ip)]
          [port-name (p) "two-way"]
          [read-char (p) (read-char ip)]
          [unread-char (c p) (unread-char c ip)]
          [write-char (c p) (write-char c op)]
          [else (assertion-violationf 'two-way-port
                  "operation ~s not handled"
                  msg)])))
    (make-input/output-port handler "" "")))

Most of the messages are passed directly to one of the constituent ports. Exceptions are close-port, which is handled directly by marking the port closed, port-name, which is also handled directly. file-position and file-length are rather arbitrarily passed off to the input port. 大多数消息都直接传递给其中一个组成端口。close-port 是个例外,它通过把端口标记为关闭来直接处理,port-name, 也是直接处理。file-position 和 file-length 则直接传递给输入端口。

Transcript ports. The next example defines make-transcript-port, which constructs a textual input/output port from three ports: a textual input port ip and two textual output ports, op and tp. Input read from a transcript port comes from ip, and output written to a transcript port goes to op. In this manner, transcript ports are similar to two-way ports. Unlike two-way ports, input from ip and output to op is also written to tp, so that tp reflects both input from ip and output to op. 转录端口。下一个示例定义了 make-transcript-port,它从三个端口构建文本输入/输出端口:文本输入端口 ip 和两个文本输出端口 op 和 tp. 从转录端口读取的输入来自 ip,写入转录端口的输出发送到 op. 这种方式下,转录端口类似于双向端口。与双向端口不同的是,来自 ip 的输入和发送到 op 的输出也写入到 tp, 因此 tp 反映了来自 ip 的输入和发送到 op 的输出。

Transcript ports may be used to define the Scheme procedures transcript-on and transcript-off, or the Chez Scheme procedure transcript-cafe. For example, here is a definition of transcript-cafe: 转录端口可用于定义 Scheme 过程 transcript-on 和 transcript-off,或 Chez Scheme 过程 transcript-cafe. 例如,以下是 transcript-cafe 的一个定义:

(define transcript-cafe
  (lambda (pathname)
    (let ([tp (open-output-file pathname 'replace)])
      (let ([p (make-transcript-port
                 (console-input-port)
                 (console-output-port)
                 tp)])
       ; set both console and current ports so that
       ; the waiter and read/write will be in sync
        (parameterize ([console-input-port p]
                       [console-output-port p]
                       [current-input-port p]
                       [current-output-port p])
          (let-values ([vals (new-cafe)])
            (close-port p)
            (close-port tp)
            (apply values vals)))))))

The implementation of transcript ports is significantly more complex than the implementation of two-way ports defined above, primarily because it buffers input and output locally. Local buffering is needed to allow the transcript file to reflect accurately the actual input and output performed in the presence of unread-char, clear-output-port, and clear-input-port. Here is the code: 转录端口的实现比上面定义的双向端口的实现复杂得多,主要是因为它在本地缓冲输入和输出。需要本地缓冲以允许转录文件准确反映在存在 unread-char,clear-output-port 和 clear-input-port 时执行的实际输入和输出。代码如下:

(define make-transcript-port
  (lambda (ip op tp)
    (define (handler msg . args)
      (record-case (cons msg args)
        [block-read (p str cnt)
         (with-interrupts-disabled
           (let ([b (port-input-buffer p)]
                 [i (port-input-index p)]
                 [s (port-input-size p)])
             (if (< i s)
                 (let ([cnt (fxmin cnt (fx- s i))])
                   (do ([i i (fx+ i 1)]
                        [j 0 (fx+ j 1)])
                       ((fx= j cnt)
                        (set-port-input-index! p i)
                        cnt)
                       (string-set! str j (string-ref b i))))
                 (let ([cnt (block-read ip str cnt)])
                   (unless (eof-object? cnt)
                     (block-write tp str cnt))
                   cnt))))]
        [char-ready? (p)
         (or (< (port-input-index p) (port-input-size p))
             (char-ready? ip))]
        [clear-input-port (p)
         ; set size to zero rather than index to size
         ; in order to invalidate unread-char
         (set-port-input-size! p 0)]
        [clear-output-port (p)
         (set-port-output-index! p 0)]
        [close-port (p)
         (with-interrupts-disabled
           (flush-output-port p)
           (set-port-output-size! p 0)
           (set-port-input-size! p 0)
           (mark-port-closed! p))]
        [file-position (p . pos)
         (if (null? pos)
             (most-negative-fixnum)
             (assertion-violationf 'transcript-port "cannot reposition"))]
        [flush-output-port (p)
         (with-interrupts-disabled
           (let ([b (port-output-buffer p)]
                 [i (port-output-index p)])
             (unless (fx= i 0)
               (block-write op b i)
               (block-write tp b i)
               (set-port-output-index! p 0)
               (set-port-bol! p
                 (char=? (string-ref b (fx- i 1)) #\newline))))
           (flush-output-port op)
           (flush-output-port tp))]
        [peek-char (p)
         (with-interrupts-disabled
           (let ([b (port-input-buffer p)]
                 [i (port-input-index p)]
                 [s (port-input-size p)])
             (if (fx< i s)
                 (string-ref b i)
                 (begin
                   (flush-output-port p)
                   (let ([s (block-read ip b)])
                     (if (eof-object? s)
                         s
                         (begin
                           (block-write tp b s)
                           (set-port-input-size! p s)
                           (string-ref b 0))))))))]
        [port-name (p) "transcript"]
        [constituent-ports (p) (values ip op tp)]
        [read-char (p)
         (with-interrupts-disabled
           (let ([c (peek-char p)])
             (unless (eof-object? c)
               (set-port-input-index! p
                 (fx+ (port-input-index p) 1)))
             c))]
        [unread-char (c p)
         (with-interrupts-disabled
           (let ([b (port-input-buffer p)]
                 [i (port-input-index p)]
                 [s (port-input-size p)])
             (when (fx= i 0)
               (assertion-violationf 'unread-char
                 "tried to unread too far on ~s"
                 p))
             (set-port-input-index! p (fx- i 1))
             ; following could be skipped; it's supposed
             ; to be the same character anyway
             (string-set! b (fx- i 1) c)))]
        [write-char (c p)
         (with-interrupts-disabled
           (let ([b (port-output-buffer p)]
                 [i (port-output-index p)]
                 [s (port-output-size p)])
             (string-set! b i c)
            ; could check here to be sure that we really
            ; need to flush; we may end up here even if
            ; the buffer isn't full
             (block-write op b (fx+ i 1))
             (block-write tp b (fx+ i 1))
             (set-port-output-index! p 0)
             (set-port-bol! p (char=? c #\newline))))]
        [block-write (p str cnt)
         (with-interrupts-disabled
          ; flush buffered data
           (let ([b (port-output-buffer p)]
                 [i (port-output-index p)])
             (unless (fx= i 0)
               (block-write op b i)
               (block-write tp b i)
               (set-port-output-index! p 0)
               (set-port-bol! p (char=? (string-ref b (fx- i 1)) #\newline))))
          ; write new data
           (unless (fx= cnt 0)
             (block-write op str cnt)
             (block-write tp str cnt)
             (set-port-bol! p
               (char=? (string-ref str (fx- cnt 1)) #\newline))))]
        [else (assertion-violationf 'transcript-port
                "operation ~s not handled"
                msg)]))
    (let ([ib (make-string 1024)] [ob (make-string 1024)])
      (let ([p (make-input/output-port handler ib ob)])
        (set-port-input-size! p 0)
        (set-port-output-size! p (fx- (string-length ob) 1))
        p))))

The chosen length of both the input and output ports is the same; this is not necessary. They could have different lengths, or one could be buffered locally and the other not buffered locally. Local buffering could be disabled effectively by providing zero-length buffers. 输入和输出端口选择的长度是相同的; 这不是必须的。它们可以具有不同的长度,或者可以一个在本地缓冲而另一个不在本地缓冲。通过提供零长度缓冲区可以有效地禁用本地缓冲。

After we create the port, the input size is set to zero since there is not yet any data to be read. The port output size is set to one less than the length of the buffer. This is done so that write-char always has one character position left over into which to write its character argument. Although this is not necessary, it does simplify the code somewhat while allowing the buffer to be flushed as soon as the last character is available. 创建端口后,输入大小设置为零,因为此时还没有要读取的数据。端口输出大小设置为缓冲区长度减一。这样做是为了使 write-char 总有一个剩余的字符位置来写入其字符参数。虽然这不是必须的,但它确实在一定程度上简化了代码,同时允许缓冲区在最后一个字符可用时可以立即刷新。

Block reads and writes are performed on the constituent ports for efficiency and (in the case of writes) to ensure that the operations are performed immediately. 为了提高效率,以及(在写入的情况下)确保操作立即执行,块读取和写入是在组成端口上执行。

The call to flush-output-port in the handling of read-char insures that all output written to op appears before input is read from ip. Since block-read is typically used to support higher-level operations that are performing their own buffering, or for direct input and output in support of I/O-intensive applications, the flush call has been omitted from that part of the handler. 在处理 read-char 时调用 flush-output-port 确保在从 ip 读取输入之前把所有输出写入到 op. 由于 block-read 通常用于支持执行其自身缓冲的更高级别操作,或者用于支持 I/O 密集型应用的直接输入和输出,因此从处理程序的该部分省略了刷新调用。

Critical sections are used whenever the handler manipulates one of the buffers, to protect against untimely interrupts that could lead to reentry into the handler. The critical sections are unnecessary if no such reentry is possible, i.e., if only one "thread" of the computation can have access to the port. 只要处理程序操作其中一个缓冲区,就会使用临界区块,以防止可能导致重新进入处理程序的过早中断。如果不可能有这样的重新进入,即,如果计算中只有一个“线程”可以访问该端口,则不需要临界区块。

Process ports. The final example demonstrates how to incorporate the socket interface defined in Section 4.9 into a generic port that allows transparent communication with subprocesses via normal Scheme input/output operations. 进程端口 。最后一个示例演示了如何将第 4.9 节中定义的套接字接口合并到一个通用端口中,该端口允许通过标准的 Scheme 输入/输出操作与子进程进行透明通信。

A process port is created with open-process, which accepts a shell command as a string. open-process sets up a socket, forks a child process, sets up two-way communication via the socket, and invokes the command in a subprocess. 进程端口由 open-process 创建,该过程的参数是一个字符串形式的 shell 命令。open-process 设置一个套接字,fork 一个子进程,通过套接字建立双向通信,并在子进程中调用该命令。

The sample session below demonstrates the use of open-process, running and communicating with another Scheme process started with the "-q" switch to suppress the greeting and prompts. 下面的示例会话演示了 open-process 的用法,运行并与另一个使用“-q”开关启动的 Scheme 进程进行通信,以废止问候语和提示符。

> (define p (open-process "exec scheme -q"))
> (define s (make-string 1000 #\nul))
> (pretty-print '(+ 3 4) p)
> (read p)
7
> (pretty-print '(define (f x) (if (= x 0) 1 (* x (f (- x 1))))) p)
> (pretty-print '(f 10) p)
> (read p)
3628800
> (pretty-print '(exit) p)
> (read p)
#!eof
> (close-port p)

Since process ports, like transcript ports, are two-way, the implementation is somewhat similar. The main difference is that a transcript port reads from and writes to its subordinate ports, whereas a process port reads from and writes to a socket. When a process port is opened, the socket is created and subprocess invoked, and when the port is closed, the socket is closed and the subprocess is terminated. 由于进程端口和转录端口一样是双向的,因此实现方式有些类似。主要区别在于,转录端口读取和写入其下级端口,而进程端口读取和写入套接字。打开进程端口时,将创建套接字并调用子进程,当端口关闭时,将关闭套接字并终止子进程。

(define open-process
  (lambda (command)
    (define handler
      (lambda (pid socket)
        (define (flush-output who p)
          (let ([i (port-output-index p)])
            (when (fx> i 0)
              (check who (c-write socket (port-output-buffer p) i))
              (set-port-output-index! p 0))))
        (lambda (msg . args)
          (record-case (cons msg args)
            [block-read (p str cnt)
             (with-interrupts-disabled
               (let ([b (port-input-buffer p)]
                     [i (port-input-index p)]
                     [s (port-input-size p)])
                 (if (< i s)
                     (let ([cnt (fxmin cnt (fx- s i))])
                       (do ([i i (fx+ i 1)]
                            [j 0 (fx+ j 1)])
                          ((fx= j cnt)
                           (set-port-input-index! p i)
                           cnt)
                          (string-set! str j (string-ref b i))))
                     (begin
                       (flush-output 'block-read p)
                       (let ([n (check 'block-read
                                  (c-read socket str cnt))])
                         (if (fx= n 0)
                             #!eof
                             n))))))]
            [char-ready? (p)
             (or (< (port-input-index p) (port-input-size p))
                 (bytes-ready? socket))]
            [clear-input-port (p)
             ; set size to zero rather than index to size
             ; in order to invalidate unread-char
             (set-port-input-size! p 0)]
            [clear-output-port (p) (set-port-output-index! p 0)]
            [close-port (p)
             (with-interrupts-disabled
               (flush-output 'close-port p)
               (set-port-output-size! p 0)
               (set-port-input-size! p 0)
               (mark-port-closed! p)
               (terminate-process pid))]
            [file-length (p) 0]
            [file-position (p . pos)
             (if (null? pos)
                 (most-negative-fixnum)
                 (assertion-violationf 'process-port "cannot reposition"))]
            [flush-output-port (p)
             (with-interrupts-disabled
               (flush-output 'flush-output-port p))]
            [peek-char (p)
             (with-interrupts-disabled
               (let ([b (port-input-buffer p)]
                     [i (port-input-index p)]
                     [s (port-input-size p)])
                 (if (fx< i s)
                     (string-ref b i)
                     (begin
                       (flush-output 'peek-char p)
                       (let ([s (check 'peek-char
                                  (c-read socket b (string-length b)))])
                         (if (fx= s 0)
                             #!eof
                             (begin (set-port-input-size! p s)
                                    (string-ref b 0))))))))]
            [port-name (p) "process"]
            [read-char (p)
             (with-interrupts-disabled
               (let ([b (port-input-buffer p)]
                     [i (port-input-index p)]
                     [s (port-input-size p)])
                 (if (fx< i s)
                     (begin
                       (set-port-input-index! p (fx+ i 1))
                       (string-ref b i))
                     (begin
                       (flush-output 'peek-char p)
                       (let ([s (check 'read-char
                                  (c-read socket b (string-length b)))])
                         (if (fx= s 0)
                             #!eof
                             (begin (set-port-input-size! p s)
                                    (set-port-input-index! p 1)
                                    (string-ref b 0))))))))]
            [unread-char (c p)
             (with-interrupts-disabled
               (let ([b (port-input-buffer p)]
                     [i (port-input-index p)]
                     [s (port-input-size p)])
                 (when (fx= i 0)
                   (assertion-violationf 'unread-char
                     "tried to unread too far on ~s"
                     p))
                 (set-port-input-index! p (fx- i 1))
                ; following could be skipped; supposed to be
                ; same character
                 (string-set! b (fx- i 1) c)))]
            [write-char (c p)
             (with-interrupts-disabled
               (let ([b (port-output-buffer p)]
                     [i (port-output-index p)]
                     [s (port-output-size p)])
                 (string-set! b i c)
                 (check 'write-char (c-write socket b (fx+ i 1)))
                 (set-port-output-index! p 0)))]
            [block-write (p str cnt)
             (with-interrupts-disabled
              ; flush buffered data
               (flush-output 'block-write p)
              ; write new data
               (check 'block-write (c-write socket str cnt)))]
            [else
             (assertion-violationf 'process-port
               "operation ~s not handled"
               msg)]))))
    (let* ([server-socket-name (tmpnam 0)]
           [server-socket (setup-server-socket server-socket-name)])
      (dofork 
        (lambda () ; child
          (check 'close (close server-socket))
          (let ([sock (setup-client-socket server-socket-name)])
            (dodup 0 sock)
            (dodup 1 sock))
          (check 'execl (execl4 "/bin/sh" "/bin/sh" "-c" command))
          (assertion-violationf 'open-process "subprocess exec failed"))
        (lambda (pid) ; parent
          (let ([sock (accept-socket server-socket)])
            (check 'close (close server-socket))
            (let ([ib (make-string 1024)] [ob (make-string 1024)])
              (let ([p (make-input/output-port
                         (handler pid sock)
                         ib ob)])
                (set-port-input-size! p 0)
                (set-port-output-size! p (fx- (string-length ob) 1))
                p))))))))

Chez Scheme Version 9 User's Guide Copyright © 2018 Cisco Systems, Inc. Licensed under the Apache License Version 2.0 (full copyright notice.). Revised January 2019 for Chez Scheme Version 9.5.1 about this book

results matching ""

    No results matching ""