7. 输入输出

所有输入和输出操作都是通过端口进行的。端口是一个指向数据(常常是文件)流(可能无穷)的指针,通过这个通道,程序可以从流中读取字节或字符,或向流中写入字节或字符。端口可以是输入端口,输出端口或双向端口。

端口是第一类对象,和 Scheme 中的任何其它对象一样。和过程相似,端口也没有像字符串和数字那样的打印形式。原始的端口有三种:当前输入端口,当前输出端口,及当前错误端口,它们是连接到进程的标准输入,标准输出,和标准错误流的文本端口。Scheme 提供了多种打开新端口的方式。

一个输入端口通常指向一个有限的流,例如,储存在硬盘上的一个输入文件。如果一个输入操作,例如, get-u8, get-char, 或 get-datum, 请求读取的部分到达了有限流的末尾,则它返回一个特殊的 eof (文件结尾)对象。谓词 eof-object? 可以用于判定输入操作返回的值是否是 eof 对象。

端口或是二进制,或是文本的。二进制端口支持程序从其所代表的流中,读取或写入 8 位无符号字节,或称 "octets"。文本端口支持程序读取或写入字符。

很多情况下,底层的流被组织为一个字节序列,但这些字节应当被作为字符编码处理。在这种情况下,可以通过编码转换器创建一个文本端口,把字节解码为字符(对于输入端口),或把字符编码为字节(对于输出端口)。编码转换器封装了一个编码解码器,它决定了字符如何表示为字节。Scheme 提供了三种标准编码解码器: latin-1 编码解码器,Unicode utf-8 编码解码器,以及 Unicode utf-16 编码解码器。在 latin-1 编码中,每个字符以正好一个字节表示。在 utf-8 编码中,每个字符以一至四个字节表示,而在 utf-16 编码中,每个字符以二或四个字节表示。

编码转换器同时封装了 eol 风格,它决定了是否以及如何识别行尾。如果 eol 风格为 none ,则不识别行尾。另外六种标准 eol 风格如下所示:

lf: line-feed character(换行字符) cr: carriage-return character(回车字符) nel: Unicode next-line character(Unicode 新行字符) ls: Unicode line-separator character(Unicode 行分隔字符) crlf: carriage return followed by line feed(回车加换行字符) crnel: carriage return followed by next line(回车加新行字符)

eol 风格对输入和输出操作的影响是不同的。对于输入,除了 none 以外的任何 eol 风格,会把每种行尾字符或双字符序列转换为一个单个的换行字符。对于输出,除了 none 以外的任何 eol 风格,会把换行字符转换为与 eol 风格相关联的特定单个字符或双字符序列。在输入方向上,除了 none 以外的所有 eol 风格都是等价的,而在输出方向上,eol 风格 nonelf 是等价的。

除了编码解码器和 eol 风格,编码转换器只封装了另外一种信息:一个错误处理模式,它决定了在解码或编码错误出现时会发生什么,即,如果通过封装的编码解码器,在输入方向上,无法把一个字节序列转换成一个字符,或在输出方向上,无法把一个字符转换成一个字节序列。错误处理模式为 ignore , raise , 或 replace . 如果错误处理模式为 ignore , 则忽略出错的字节序列或字符。如果错误处理模式为 raise , 则抛出条件类型的 i/o-decodingi/o-encoding 异常;在输入方向上,端口的位置在字节序列前面。如果错误处理模式为 replace , 会生成一个替换字符或字符编码:在输入方向上,替换字符是 U+FFFD, 而在输出方向上,则替换编码为 utf-8utf-16 编码解码器的 U+FFFD 的编码,或是 latin-1 编码解码器的问号字符(?)的编码。

为了提高效率,可以缓冲端口,以减少为每个字节或字符进行系统调用的开销。Scheme 支持三种标准缓冲模式: block , line , 和 none . 在 block 缓冲模式中,是以块的形式从流中读取输入或向流发送输出,块的大小与实现相关。在 line 缓冲模式中,缓冲以行为基础,或以其它某些实现相关的基础进行。 Line 缓冲通常只在文本输出端口中与 block 缓冲有区别;在二进制端口中没有行划分,而输入常常是在它可用时即从流中读取。在 none 缓冲模式中,不进行缓冲,所以输出会立即发送到流,而输入则只在需要时读取。

本章的其余部分包含了编码转换器操作,文件端口,标准端口,输出操作,快捷 I/O, 文件系统操作,字节向量和字符串之间的转换。

7.1. 编码转换器

如上所述,编码转换器封装了三个值:编码解码器,eol 风格,和错误处理模式。本节介绍了创建和操作编码转换器的过程,以及编码转换器封装的值。

过程: (make-transcoder codec) 过程: (make-transcoder codec eol-style) 过程: (make-transcoder codec eol-style error-handling-mode) 返回: 封装了编码解码器,eol 风格,和错误处理模式的编码转换器 库: (rnrs io ports), (rnrs)

eol-style 必须是一个有效的 eol 风格符号 (lf, cr , nel , ls , crlf , crnel , 或 none); 它默认为平台的原生 eol 风格。 error-handling-mode 必须是一个有效的错误处理模式符号 (ignore, raise , 或 replace),默认值为 replace .

过程: (transcoder-codec transcoder) 返回: transcoder 封装的编码解码器 过程: (transcoder-eol-style transcoder) 返回: transcoder 封装的 eol 风格符号 过程: (transcoder-error-handling-mode transcoder) 返回: transcoder 封装的错误处理模式符号 库: (rnrs io ports), (rnrs)

过程: (native-transcoder) 返回: 原生的编码转换器 库: (rnrs io ports), (rnrs)

原生的编码转换器是依赖于实现的,且可能根据平台或区域设置有所变化。

过程: (latin-1-codec) 返回: ISO 8859-1 (Latin 1) 字符编码的编码解码器 过程: (utf-8-codec) 返回: Unicode UTF-8 字符编码的编码解码器 过程: (utf-16-codec) 返回: Unicode UTF-16 字符编码的编码解码器 库: (rnrs io ports), (rnrs)

syntax: (eol-style symbol) 返回: symbol 库: (rnrs io ports), (rnrs)

symbol 必须是以下符号之一: lf , cr , nel , ls , crlf , crnel , 或 none . 表达式 (eol-style symbol) 等价于表达式 (quote symbol), 只是前者在展开期会检查 symbol 是否是 eol 风格符号之一。 eol-style 语法也提供了有用的文档。

(eol-style crlf) => crlf
(eol-style lfcr) => syntax violation

过程: (native-eol-style) 返回: 原生的 eol 风格 库: (rnrs io ports), (rnrs)

原生的 eol 风格是依赖于实现的,且可能根据平台或区域设置有所变化。

syntax: (error-handling-mode symbol) 返回: symbol 库: (rnrs io ports), (rnrs)

symbol 必须是以下符号之一: ignore , raise , 或 replace . 表达式 (error-handling-mode symbol) 等价于表达式 (quote symbol), 除了前者在展开期会检查 symbol 是否是错误处理模式符号之一。 error-handling-mode 语法也提供了有用的文档。

(error-handling-mode replace) => replace
(error-handling-mode relpace) => syntax violation

7.2. 打开文件

本节中的过程用于打开文件端口。用于打开其它类型端口的过程,例如,字符串端口,或自定义端口,将在后面几节中介绍。

每个打开文件的操作接受一个路径实参,指定了要打开的文件。它必须是命名了一个文件的字符串或其它某些依赖于实现的值。

某些文件打开操作接受可选的 options, b-mode, 和 ?transcoder 实参。 options 必须是一个枚举集合,由下文 file-options 条目中介绍的有效文件选项符号组成,默认值为 (file-options) 的值。 b-mode 必须是下文 buffer-mode 条目中介绍的有效缓冲模式,默认值为 block . ?transcoder 必须是一个编码转换器,或 #f ; 如果它是一个编码转换器,则打开操作返回底层二进制文件的编码转换后的端口,如果它是 #f (默认值),则打开操作返回一个二进制端口。

由本节中的过程创建的二进制端口,支持 port-positionset-port-position! 操作。而由本节中的过程创建的文本端口,是否支持这些操作则依赖于实现。

syntax: (file-options symbol ...) 返回: 一个文件选项的枚举集合 库: (rnrs io ports), (rnrs)

文件选项枚举集合可以传入文件打开操作,以从多方面控制打开操作。有三种标准文件选项: no-create , no-fail , 以及 no-truncate , 这些选项只影响创建输出(包括输入/输出)端口的文件打开操作。

文件选项为默认值时,即 (file-option) 的值,当程序尝试打开一个文件作为输出时,如果此文件已经存在,则会抛出一个条件类型的 i/o-file-already-exists 异常,如果文件不存在,则会创建此文件。如果包含 no-fail 选项,则在文件已经存在时不会抛出异常;而是打开此文件,并把它的长度截短为 0. 如果包含 no-create 选项,则文件不存在时不会创建此文件;而是抛出一个条件类型的 i/o-file-dose-not-exist 异常。 no-create 选项暗含了 no-fail 选项。只在包含或暗含 no-fail 选项时, no-truncate 选项才有意义,此时如果打开一个已经存在的文件,它不会被截短,但是,端口的位置仍然被设定于文件的起始处。

可能不难想象,默认的文件选项是虚构的选项符号 create , fail-if-exists , 和 truncate ; no-create 移除 create , no-fail 移除 fail-if-exists , 而 no-truncate 移除 truncate .

具体实现可能支持额外的文件选项符号。例如,Chez Scheme 支持选项控制文件是否应被压缩,是否被锁定以进行独占访问,以及文件创建时被给予什么权限[9]。

syntax: (buffer-mode symbol) 返回: symbol 库: (rnrs io ports), (rnrs)

symbol 必须是以下符号之一: block , line , 或 none . 表达式 (buffer-mode symbol) 等价于表达式 (quote symbol), 只是前者在展开期会检查 symbol 是否是缓冲模式符号之一。 buffer-mode 语法也提供了有用的文档。

(buffer-mode block) => block
(buffer-mode cushion) => syntax violation

syntax: (buffer-mode? obj) 返回: 如果 obj 是一个有效的缓冲模式,则为 #t, 否则为 #f. 库: (rnrs io ports), (rnrs)

(buffer-mode? 'block) => #t
(buffer-mode? 'line) => #t
(buffer-mode? 'none) => #t
(buffer-mode? 'something-else) => #f

过程: (open-file-input-port path) 过程: (open-file-input-port path options) 过程: (open-file-input-port path options b-mode) 过程: (open-file-input-port path options b-mode ?transcoder) 返回: 指定文件的新输入端口 库: (rnrs io ports), (rnrs)

如果指定了 ?transcoder, 且不为 #f, 则它必须是一个编码转换器,而这个过程返回一个文本输入端口,它的编码转换器为 ?transcoder. 否则,这个过程返回一个二进制输入端口。关于其它实参的约束条件和影响,请参见本节引言中的介绍。

过程: (open-file-output-port path) 过程: (open-file-output-port path options) 过程: (open-file-output-port path options b-mode) 过程: (open-file-output-port path options b-mode ?transcoder) 返回: 指定文件的新输出端口 库: (rnrs io ports), (rnrs)

如果指定了 ?transcoder, 且不为 #f, 则它必须是一个编码转换器,而这个过程返回一个文本输出端口,它的编码转换器为 ?transcoder. 否则,这个过程返回一个二进制输出端口。关于其它实参的约束条件和影响,请参见本节引言中的介绍。

过程: (open-file-input/output-port path) 过程: (open-file-input/output-port path options) 过程: (open-file-input/output-port path options b-mode) 过程: (open-file-input/output-port path options b-mode ?transcoder) 返回: a new input/output port for the named file 库: (rnrs io ports), (rnrs)

如果指定了 ?transcoder, 且不为 #f, 则它必须是一个编码转换器,而这个过程返回一个文本输入/输出端口,它的编码转换器为 ?transcoder. 否则,这个过程返回一个二进制输入/输出端口。关于其它实参的约束条件和影响,请参见本节引言中的介绍。

7.3. 标准端口

本节介绍的过程返回附加到进程的标准输入,标准输出,和标准错误流上的端口。第一组过程返回“现成的”文本端口,带有依赖于实现的编码转换器(如果存在的话)和缓冲模式。第二组过程集创建新的二进制端口,可用于二进制输入/输出,或在 transcoded-port 的帮助下,用于带有程序提供的编码转换器和缓冲模式的文本输入/输出。

过程: (current-input-port) 返回: 当前输入端口 过程: (current-output-port) 返回: 当前输出端口 过程: (current-error-port) 返回: 当前错误端口 库: (rnrs io ports), (rnrs io simple), (rnrs)

当前输入,当前输出,和当前错误端口返回预设的文本端口,这些端口最初与进程的标准输入,标准输出,和标准错误流相关联。

可以通过快捷 I/O 过程 with-input-from-filewith-output-to-file (7.9 节)临时替代 current-input-portcurrent-output-port 返回的值。

过程: (standard-input-port) 返回: 连接到标准输入流的一个全新的二进制输入端口 过程: (standard-output-port) 返回: 连接到标准输出流的一个全新的二进制输出端口 过程: (standard-error-port) 返回: 连接到标准错误流的一个全新的二进制输出端口 库: (rnrs io ports), (rnrs)

由于端口可能是缓冲的,因此如果连接到某个进程标准流的多个端口上的操作是交错的,则会导致混淆。 因此,这些过程通常仅在程序不再需要使用附加到标准流的任何现有端口时才适用。

7.4. 字符串和字节向量端口

本节中的过程允许把字节向量和字符串用作输入和输出流。

本节中的过程创建的二进制端口,支持 port-positionset-port-position! 操作。本节中的过程创建的文本端口是否支持这些操作,则依赖于实现。

过程: (open-bytevector-input-port bytevector) 过程: (open-bytevector-input-port bytevector ?transcoder) 返回: 从 bytevector 获取输入的新输入端口 库: (rnrs io ports), (rnrs)

如果传入 ?transcoder, 且其值不是 #f, 则它必须是一个编码转换器,而此过程返回一个文本输入端口,端口的编码转换器为 ?transcoder. 否则,此过程返回一个二进制输入端口。

在调用这个过程之后修改 bytevector 的效果是未定义的。

(let ([ip (open-bytevector-input-port #vu8(1 2))])
  (let* ([x1 (get-u8 ip)] [x2 (get-u8 ip)] [x3 (get-u8 ip)])
    (list x1 x2 (eof-object? x3)))) => (1 2 #t)

没有必要关闭一个字节向量端口;当不再需要时,它的存储会被自动回收,和对任何其它对象一样。而且,一个打开的字节向量端口不会绑定任何操作系统资源。

过程: (open-string-input-port string) 返回: 从 string 获取输入的新文本输入端口 库: (rnrs io ports), (rnrs)

在调用这个过程之后修改 string 的效果是未定义的。新端口可能有也可能没有编码转换器,而如果它有的话,此编码转换器是依赖于实现的。虽然不是强制要求,但实现最好支持字符串端口的 port-positionset-port-position! 操作。

(get-line (open-string-input-port "hi.\nwhat's up?\n")) => "hi."

没有必要关闭一个字符串端口;当不再需要时,它的存储会被自动回收,和对任何其它对象一样。而且,一个打开的字符串端口不会绑定任何操作系统资源。

过程: (open-bytevector-output-port) 过程: (open-bytevector-output-port ?transcoder) 返回: 两个值——一个新的输出端口和一个提取过程 库: (rnrs io ports), (rnrs)

如果传入 ?transcoder, 且其值不是 #f, 则它必须是一个编码转换器,且端口值是一个文本输出端口,其编码转换器为 ?transcoder. 否则,端口值为一个二进制输出端口。

提取过程是一个过程,当不带实参调用时,创建一个字节向量,其中包含端口中累积的字节,然后把端口累积的字节清空,把它的位置重置为 0, 并返回它创建的字节向量。如果当前位置被设回它的最大范围以内,累积的字节也包括当前位置后面写入的所有字节。

(let-values ([(op g) (open-bytevector-output-port)])
  (put-u8 op 15)
  (put-u8 op 73)
  (put-u8 op 115)
  (set-port-position! op 2)
  (let ([bv1 (g)])
    (put-u8 op 27)
    (list bv1 (g)))) => (#vu8(15 73 115) #vu8(27))

没有必要关闭一个字节向量端口;当不再需要时,它的存储会被自动回收,和对任何其它对象一样。而且,一个打开的字节向量端口不会绑定任何操作系统资源。

过程: (open-string-output-port) 返回: 两个值——一个新的文本输出端口和一个提取过程 库: (rnrs io ports), (rnrs)

提取过程是一个过程,当不带实参调用时,创建一个字符串,其中包含端口中累积的字符,然后把端口累积的字符清空,把它的位置重置为 0, 并返回它创建的字符串。如果当前位置被设回它的最大范围以内,累积的字符也包括当前位置后面写入的所有字符。虽然不是强制要求,但实现最好支持字符串端口的 port-positionset-port-position! 操作。

(let-values ([(op g) (open-string-output-port)])
  (put-string op "some data")
  (let ([str1 (g)])
    (put-string op "new stuff")
    (list str1 (g)))) => ("some data" "new stuff")

没有必要关闭一个字符串端口;当不再需要时,它的存储会被自动回收,和对任何其它对象一样。而且,一个打开的字符串端口不会绑定任何操作系统资源。

过程: (call-with-bytevector-output-port procedure) 过程: (call-with-bytevector-output-port procedure ?transcoder) 返回: 包含累积字节的字节向量 库: (rnrs io ports), (rnrs)

如果传入 ?transcoder, 且其值不为 #f, 则它必须是一个编码转换器,而 procedure 由一个文本字节向量输出端口调用,它的编码转换器为 ?transcoder. 否则, procedure 由一个二进制字节向量输出端口调用。如果 procedure 返回,则创建包含端口中累积的字节的字节向量,从端口清除累积的字节,将端口的位置重置为零,并由 call-with-bytevector-output-port 返回字节向量。如果由于在 procedure 处于活动状态时调用创建的 continuation 而多次返回,则每次 procedure 返回时都会发生这些操作。

(let ([tx (make-transcoder (latin-1-codec) (eol-style lf)
            (error-handling-mode replace))])
  (call-with-bytevector-output-port
    (lambda (p) (put-string p "abc"))
    tx)) => #vu8(97 98 99)

过程: (call-with-string-output-port procedure) 返回:包含累积字符的字符串 库: (rnrs io ports), (rnrs)

procedure is called with one argument, a string output port. If procedure returns, a string containing the characters accumulated in the port is created, the accumulated characters are cleared from the port, the port's position is reset to zero, and the string is returned from call-with-string-output-port. These actions occur each time procedure returns, if it returns multiple times due to the invocation of a continuation created while procedure is active. procedure 调用时需要一个参数——一个字符串输出端口。如果 procedure 返回,则创建包含端口中累积的字符的字符串,从端口清除累积的字符,将端口的位置重置为零,并从 call-with-string-output-port 返回该字符串。如果由于在 procedure 处于活动状态时调用创建的 continuation 而多次返回,则每次 procedure 返回时都会发生这些操作。

call-with-string-output-port can be used along with put-datum to define a procedure, object->string, that returns a string containing the printed representation of an object. call-with-string-output-port 可以与 put-datum 一起使用来定义一个过程 object->string, 它返回一个包含对象打印表示形式的字符串。

(define (object->string x)
  (call-with-string-output-port
    (lambda (p) (put-datum p x))))

(object->string (cons 'a '(b c))) => "(a b c)"

7.5. 打开自定义端口

过程: (make-custom-binary-input-port id r! gp sp! close) 返回: 新的自定义二进制输入端口 过程: (make-custom-binary-output-port id w! gp sp! close) 返回: 新的自定义二进制输出端口 过程: (make-custom-binary-input/output-port id r! w! gp sp! close) 返回: 新的自定义二进制输入/输出端口 库: (rnrs io ports), (rnrs)

这些过程允许程序通过任意字节流创建端口。 id 必须是一个命名新端口的字符串;名字只是用于信息用途,而实现可以选择把它包含在自定义端口的打印语法形式(如果存在的话)中。 r!w! 必须是过程,而 gp, sp!close 均必须为过程或 #f. 这些实参介绍如下。

r! 为了从自定义端口获取输入而调用,例如,为了支持 get-u8get-bytevector-n. 调用时传入三个实参: bytevector, start, 和 n. start 会是一个非负精确整数, n 会是一个正的精确整数,而 startn 的和不能超过 bytevector 的长度。如果字节流在文件的结尾, r! 应该返回精确的 0. 否则,它应该从流中读入最少 1 个,最多 n 个字节,把这些字节存储在 bytevector 起始于 start 的连续位置上,并把实际读入的字节数作为一个精确的正整数返回。

w! 为了向端口发送输出而调用,例如,为了支持 put-u8put-bytevector. 调用时传入三个实参: bytevector, start, 和 n. startn 会是非负精确整数,而 startn 的和不能超过 bytevector 的长度。 w! 应当写入 bytevector 中始于 start 的至多 n 个连续字节,并把实际写入的字节数作为一个精确的非负整数返回。

gp 为了查询端口位置而调用。如是它为 #f, 则端口不会支持 port-position. 如果它不为 #f, 它将被传入 0 个实参,并把字节流从开始位置起的字节偏移量作为当前位置,返回为一个精确的非负整数。

sp! 为了设置端口位置而调用。如果它为 #f, 则端口不会支持 set-port-position!. 如果它不为 #f, 它将被传入 1 个实参——一个精确的非负整数,以字节流从开始位置起的字节偏移量表示的新位置,而它应当把位置设为这个值。

close 为了关闭字节流而调用。如果它为 #f, 当新端口关闭时,不会采取任何动作以关闭字节流。如果它不为 #f, 它会被传入 0 个实参, 并会采取一切必要的动作以关闭字节流。

如果新端口是一个输入/输出端口,而且未提供 gpsp! 过程,则当输出操作发生在输入操作之后时,实现很可能无法正确定位端口,因为,为了支持 lookahead-u8 而必须完成的输入缓冲,为了效率经常会无条件完成。基于同样的原因,在一个输入操作之后调用 port-position ,如果没有提供 sp! 过程,则可能无法返回一个精确的位置。因此,创建自定义二进制输入/输出端口的程序,通常应当提供 gpsp! 过程。

过程: (make-custom-textual-input-port id r! gp sp! close) 返回: 新的自定义文本输入端口 过程: (make-custom-textual-output-port id w! gp sp! close) 返回: 新的自定义文本输出端口 过程: (make-custom-textual-input/output-port id r! w! gp sp! close) 返回: 新的自定义文本输入/输出端口 库: (rnrs io ports), (rnrs)

这些过程允许程序通过任意字符流创建端口。 id 必须是一个命名新端口的字符串;名字只是用于信息用途,而实现可以选择把它包含在自定义端口的打印语法形式(如果存在的话)中。 r!w! 必须是过程,而 gp, sp!close 均必须为过程或 #f. 这些实参介绍如下。

r! 为了从端口获取输入而调用,例如,为了支持 get-charget-string-n. 调用时传入三个实参: string, start, 和 n. start 会是一个非负精确整数, n 会是一个正的精确整数,而 startn 的和不能超过 string 的长度。如果字符流在文件的结尾, r! 应该返回精确的 0. 否则,它应该从流中读入最少 1 个,最多 n 个字符,把这些字符存储在 string 起始于 start 的连续位置上,并把实际读入的字符数作为一个精确的正整数返回。

w! 为了向端口发送输出而调用,例如,为了支持 put-charput-string. 调用时传入三个实参: string, start, 和 n. startn 会是非负精确整数,而 startn 的和不能超过 string 的长度。 w! 应当写入 string 中始于 start 的至多 n 个连续字符,并把实际写入的字符数作为一个精确的非负整数返回。

gp 为了查询端口位置而调用。如是它为 #f, 则端口不会支持 port-position. 如果它不为 #f, 它会被传入 0 个实参,并返回当前位置,其可能为任意值。

sp! 为了设置端口位置而调用。如果它为 #f, 则端口不会支持 set-port-position!. 如果它不为 #f, 它会被传入 1 个实参—— pos, 一个表示新位置的值。如果 pos 是之前对 gp 的一个调用的结果,则 sp! 应当把位置设为 pos.

close 为了关闭字符流而调用。如果它为 #f, 当新端口关闭时,不会采取任何动作以关闭字符流。如果它不为 #f, 它会被传入 0 个实参, 并会采取一切必要的动作以关闭字符流。

如果新端口是输入/输出端口,则当输出操作发生在输入操作之后时,即使提供了 gpsp! 过程,实现也很可能无法正确定位端口,因为,为了支持 lookahead-char 而必须完成的输入缓冲,为了效率经常会无条件完成。由于没有指定端口位置的表示形式,实现无法调整 gp 的返回值,以计入缓冲的字符数。基于同样的原因,在一个输入操作之后调用 port-position ,即使提供了 sp! 过程,也可能无法返回一个精确的位置。

然而,如果位置被重置为起始位置,则应该能够在读取后可靠地执行输出。因此,创建自定义文本输入/输出端口的程序,通常应当提供 gpsp! 过程,而这些端口的使用者应当在任何输入操作之前,通过 port-position 获取起始位置,并在进行任何输出操作之前,把位置重置回起始位置。

7.6. 端口操作

本节介绍了多种不直接涉及读写端口的端口操作。输入和输出操作会在后续章节中介绍。

过程: (port? obj) 返回: 如果 obj 是端口,则为 #t, 否则为 #f. 库: (rnrs io ports), (rnrs)

过程: (input-port? obj) 返回: 如果 obj 是输入端口或输入/输出端口,则为 #t, 否则为 #f. 过程: (output-port? obj) 返回: 如果 obj 是输出端口或输入/输出端口,则为 #t, 否则为 #f. 库: (rnrs io ports), (rnrs io simple), (rnrs)

过程: (binary-port? obj) 返回: 如果 obj 是二进制端口,则为 #t, 否则为 #f. 过程: (textual-port? obj) 返回: 如果 obj 是文本端口,则为 #t, 否则为 #f. 库: (rnrs io ports), (rnrs)

过程: (close-port port) 返回: 未指定 库: (rnrs io ports), (rnrs)

如果端口尚未关闭,则 close-port 会关闭它,如果端口是输出端口,首先要把所有缓存的字节或字符写入底层流。一旦端口被关闭,就不可以在端口上再进行任何的输入或输出操作。由于操作系统可能会对同时开启的文件端口数量设限,或对打开文件的访问设限,因此,关闭任何不再用于输入或输出的文件端口是个好的习惯。如果端口是输出端口,显式关闭端口同样确保了缓冲数据会被写入底层流。某些 Scheme 实现,在文件端口对程序变得不可访问时,或在 Scheme 程序退出后,会自动关闭文件端口,但最好还是尽可能显式关闭文件端口。关闭一个已经关闭的端口不会产生任何影响。

过程: (transcoded-port binary-port transcoder) 返回: 新的文本端口,有着和 binary-port 一样的字节流 库: (rnrs io ports), (rnrs)

这个过程返回一个新的文本端口,编码转换器为 transcoder, 底层字节流和 binary-port 一样,定位于 binary-port 的当前位置。

作为创建文本端口的副作用, binary-port 会被关闭,以防止在 binary-port 上的读写操作和在新的文本端口上的读写操作会互相干扰。不过,底层字节流会保持开启,直到此文本端口被关闭。

过程: (port-transcoder port) 返回: 与 port 相关联的编码转换器(如果存在的话),否则为 #f 库: (rnrs io ports), (rnrs)

这个过程对二进制端口总是返回 #f, 而对某些文本端口可能返回 #f.

过程: (port-position port) 返回: port 的当前位置 过程: (port-has-port-position? port) 返回: 如果 port 支持 port-position ,则为 #t, 否则为 #f. 库: (rnrs io ports), (rnrs)

端口可能支持查询以确定它的底层字节或字符流中的当前位置。如果是这样,过程 port-has-port-position? 返回 #t, 而 port-position 返回当前位置。对于二进制端口,位置总是一个从字节流起始位置起的精确非负整数的字节偏移量。对于文本端口,位置的表示形式是未定义的;它可能不是一个精确的非负整数,而即使它是,它可能也不表示底层流的字节或字符偏移量。如果端口支持 set-port-position!, 位置可以在之后用于重置位置。如果在一个不支持 port-position 的端口上调用它,会抛出一个条件类型的 &assertion 异常。

过程: (set-port-position! port pos) 返回: 未指定 过程: (port-has-set-port-position!? port) 返回: 如果 port 支持 set-port-position! ,则为 #t, 否则为 #f. 库: (rnrs io ports), (rnrs)

一个端口可能允许把它的当前位置直接移动到底层字节或字符流中的一个不同位置。如果这样的话,过程 port-has-set-port-position!? 返回 #t, 而 set-port-position! 改变当前位置。对于二进制端口,位置 pos 必须是一个精确非负整数的,从字节流起始位置起的字节偏移量。对于文本端口,位置的表示形式是未定义的,如同上面 port-position 条目中所介绍的,但 pos 必须是文本端口的一个适当的位置,通常只有在它是从相同端口上对 port-position 的调用中获得时,才能确保如此。如果在不支持 set-port-position! 的端口上调用它,会抛出一个条件类型的 &assertion 异常。

如果端口是二进制输出端口,而设定的位置超过了底层流中数据的当前末尾,则直到向那个位置写入新数据时,流才会扩展。如果向那个位置写入新数据,则每个中间位置的内容均为未定义的。通过 open-file-output-portopen-file-input/output-port 创建的二进制端口,总是能以这种方式扩展,只要不超出底层操作系统的限制。在其它情况下,尝试把端口设定到超出底层对象中数据的当前末尾的位置时,可能会引起条件类型的 &i/o-invalid-position 异常。

过程: (call-with-port port procedure) 返回: procedure 返回的值 库: (rnrs io ports), (rnrs)

call-with-portport 作为唯一实参调用 procedure. 如果 procedure 返回, 则 call-with-port 关闭端口,并返回 procedure 返回的值。

如果一个创建于 procedure 之外的 continuation 被调用,则 call-with-port 不会自动关闭端口,因为之后可能会有另一个创建于 procedure 之内的 continuation 被调用,并把控制权返还给 procedure. 如果 procedure 没有返回,则只有在实现可以确认此输出端口不再可以被访问时,才可以自由地关闭此端口。

以下的例子,把输入文件的内容复制到输出文件,如果输出文件已存在,则被覆盖。除非发生错误,不然在复制结束后端口即被关闭。

(call-with-port (open-file-input-port "infile" (file-options)
                  (buffer-mode block) (native-transcoder))
  (lambda (ip)
    (call-with-port (open-file-output-port "outfile"
                      (file-options no-fail)
                      (buffer-mode block)
                      (native-transcoder)) 
      (lambda (op)
        (do ([c (get-char ip) (get-char ip)])
            ((eof-object? c))
          (put-char op c))))))

call-with-port 的定义在 135 页给出。

过程: (output-port-buffer-mode port) 返回: 表示 port 缓冲模式的符号 库: (rnrs io ports), (rnrs)

7.7. 输入操作

本节介绍了主要目的是从输入端口读取数据的过程,以及识别或创建文件结尾(eof)对象的相关过程。

过程: (eof-object? obj) 返回: 如果 obj 是 eof 对象,则为 #t, 否则为 #f. 库: (rnrs io ports), (rnrs io simple), (rnrs)

当输入端口到达输入末尾时,输入操作(例如, get-datum )返回文件结束对象。

过程: (eof-object) 返回: eof 对象 库: (rnrs io ports), (rnrs io simple), (rnrs)

(eof-object? (eof-object)) => #t

过程: (get-u8 binary-input-port) 返回: binary-input-port 中的下一个字节,或 eof 对象 库: (rnrs io ports), (rnrs)

如果 binary-input-port 处于文件末尾,则返回 eof 对象。否则,会把下一个可用字节作为无符号 8 位数返回,即,一个精确的无符号整数,小于等于 255,而端口位置会增加一个字节。

过程: (lookahead-u8 binary-input-port) 返回: binary-input-port 中的下一个字节,或 eof 对象 库: (rnrs io ports), (rnrs)

如果 binary-input-port 处于文件末尾,则返回 eof 对象。否则,会把下一个可用字节作为无符号 8 位数返回,即,一个精确的无符号整数,小于等于 255. 与 get-u8 相比, lookahead-u8 不消耗它从端口读取的字节,所以,如果下一个端口上的操作是对 lookahead-u8get-u8 的调用,则会返回相同的字节。

过程: (get-bytevector-n binary-input-port n) 返回: 一个非空字节向量,包含至多 n 个字节,或 eof 对象 库: (rnrs io ports), (rnrs)

n 必须是精确的非负整数。如果 binary-input-port 处于文件末尾,则返回 eof 对象。否则, get-bytevector-n 读取(和 get-u8 一样)在端口到达文件末尾前可用的尽可能多的,至多 n 个字节,并返回一个新的(非空)的包含这些字节的字节向量。端口位置移动到所读字节的后面。

过程: (get-bytevector-n! binary-input-port bytevector start n) 返回: 读取的字节数,或 eof 对象 库: (rnrs io ports), (rnrs)

startn 必须是精确的非负整数,且 startn 的和一定不能超过 bytevector 的长度。

如果 binary-input-port 处于文件末尾,则返回 eof 对象。否则, get-bytevector-n! 读取(和 get-u8 一样)在端口到达文件末尾前可用的尽可能多的,至多 n 个字节,把这些字节存储在 bytevector 起始于 start 的连续位置上,并把读取的字节数作为一个精确的正整数返回。端口位置被移动到读取的字节之后。

过程: (get-bytevector-some binary-input-port) 返回: 一个非空字节向量,或 eof 对象 库: (rnrs io ports), (rnrs)

如果 binary-input-port 处于文件末尾,则返回 eof 对象。否则, get-bytevector-some 读取(和 get-u8 一样)至少一个,也可能多个字节,并返回一个包含这些字节的字节向量。端口位置被移动到读取的字节之后。通过这个操作读取的最大字节数是与实现相关的。

过程: (get-bytevector-all binary-input-port) 返回: 一个非空字节向量,或 eof 对象 库: (rnrs io ports), (rnrs)

如果 binary-input-port 处于文件末尾,则返回 eof 对象。否则, get-bytevector-all 读取(和 get-u8 一样)在端口到达文件末尾前可用的所有字节,并返回一个包含这些字节的字节向量。端口位置被移动到读取的字节之后。

过程: (get-char textual-input-port) 返回: textual-input-port 中的下一个字符,或 eof 对象 库: (rnrs io ports), (rnrs)

如果 textual-input-port 处于文件末尾,则返回 eof 对象。否则,会返回下一个可用字符,且端口位置会后移一个字符。如果 textual-input-port 是一个经过编码转换的端口,则底层字节流中的位置可能后移多于一个字节。

过程: (lookahead-char textual-input-port) 返回: textual-input-port 中的下一个字符,或 eof 对象 库: (rnrs io ports), (rnrs)

如果 textual-input-port 处于文件末尾,则返回 eof 对象。否则,会返回下一个可用字符。与 get-char 相比, lookahead-char 不消耗它从端口读取的字符,所以,如果下一个端口上的操作是对 lookahead-charget-char 的调用,则会返回相同的字符。

lookahead-char 是为需要预知后一个字符的应用而提供的。下方定义的 get-word 过程,把文本输入端口中的下一个单词作为字符串返回,其中单词定义为一个字母字符的序列。由于 get-word 在看到单词的后一个字符之前,它并不知道它读入了整个单词,因此它使用 lookahead-char 以确定下一个字符,而使用 get-char 以读入下一个字符。

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

过程: (get-string-n textual-input-port n) 返回: 一个包含至多 n 个字符的非空字符串,或 eof 对象 库: (rnrs io ports), (rnrs)

n 必须是精确的非负整数。如果 textual-input-port 处于文件末尾,则返回 eof 对象。否则, get-string-n 读取(和 get-char 一样)在端口到达文件末尾前可用的尽可能多的,至多 n 个字节,并返回一个新的(非空)的包含这些字符的字符串。端口位置被移动到读取的字符之后。

过程: (get-string-n! textual-input-port string start n) 返回: 读入的字符数,或 eof 对象 库: (rnrs io ports), (rnrs)

startn 必须是精确的非负整数,且 startn 的和一定不能超过 string 的长度。

如果 textual-input-port 处于文件末尾,则返回 eof 对象。否则, get-string-n! 读取(和 get-char 一样)在端口到达文件末尾前可用的尽可能多的,至多 n 个字符,把这些字符存储在 string 起始于 start 的连续位置上,并把读取的字符数作为一个精确的正整数返回。端口位置被移动到读取的字符之后。

get-string-n! 可被用于实现 string-set!string-fill!, 如下所示,不过,这并不是它的主要用途。

(define string-set!
  (lambda (s i c)
    (let ([sip (open-string-input-port (string c))])
      (get-string-n! sip s i 1)
     ; return 未指定 values:
      (if #f #f))))

(define string-fill!
  (lambda (s c)
    (let ([n (string-length s)])
      (let ([sip (open-string-input-port (make-string n c))])
        (get-string-n! sip s 0 n)
       ; return 未指定 values:
        (if #f #f)))))

(let ([x (make-string 3)])
  (string-fill! x #\-)
  (string-set! x 2 #\))
  (string-set! x 0 #\;)
  x) => ";-)"

过程: (get-string-all textual-input-port) 返回: 一个非空字符串,或 eof 对象 库: (rnrs io ports), (rnrs)

如果 textual-input-port 处于文件末尾,则返回 eof 对象。否则, get-string-all 读取(和 get-char 一样)在端口到达文件末尾前可用的所有字符,并返回一个包含这些字符的字符串。端口位置被移动到读取的字符之后。

过程: (get-line textual-input-port) 返回: 一个字符串,或 eof 对象 库: (rnrs io ports), (rnrs)

如果 textual-input-port 处于文件末尾,则返回 eof 对象。否则, get-line 读取(和 get-char 一样)在端口到达文件末尾前或读到换行字符时可用的所有字符,并返回一个包含除了换行字符以外的所有读入字符的字符串。端口位置被移动到读取的字符之后。

(let ([sip (open-string-input-port "one\ntwo\n")])
  (let* ([s1 (get-line sip)] [s2 (get-line sip)])
    (list s1 s2 (port-eof? sip)))) => ("one" "two" #t)

(let ([sip (open-string-input-port "one\ntwo")])
  (let* ([s1 (get-line sip)] [s2 (get-line sip)])
    (list s1 s2 (port-eof? sip)))) => ("one" "two" #t)

过程: (get-datum textual-input-port) 返回: 一个 Scheme datum 对象,或 eof 对象 库: (rnrs io ports), (rnrs)

这个过程扫描时略过空白和注释,寻找一个 datum 外部表示形式的起始点。如果在找到一个 datum 外部表示形式的起始点之前, textual-input-port 就到达了文件末尾,则返回 eof 对象。

否则, get-datum 根据需要读取尽可能多,却不过多的字符,以解析一个单个的 datum, 并返回一个新分配的对象,其结构由外部表示形式决定。端口位置被移动到读取的字符之后。如果在 datum 的外部表示形式完成之前就到达了文件末尾,或读取到一个意外的字符,则会抛出条件类型的 &lexicali/o-read 异常。

(let ([sip (open-string-input-port "; a\n\n one (two)\n")])
  (let* ([x1 (get-datum sip)]
         [c1 (lookahead-char sip)]
         [x2 (get-datum sip)])
    (list x1 c1 x2 (port-eof? sip)))) => (one #\space (two) #f)

过程: (port-eof? input-port) 返回: 如果 input-port 处于文件末尾,则为 #t, 否则为 #f. 库: (rnrs io ports), (rnrs)

这个过程类似于二进制输入端口上的 lookahead-u8 过程,或文本输入端口上的 lookahead-char 过程,只是它并非返回下一个字节/字符或 eof 对象,而是返回一个布尔值,以指示值是否为 eof 对象。

7.8. 输出操作

本节介绍了主要用途是向输出端口发送数据的过程。

过程: (put-u8 binary-output-port octet) 返回: 未指定 库: (rnrs io ports), (rnrs)

octet 必须是一个精确的非负整数,且小于等于 255. 这个过程把 octet 写入 binary-output-port, 并把端口位置后移一个节字。

过程: (put-bytevector binary-output-port bytevector) 过程: (put-bytevector binary-output-port bytevector start) 过程: (put-bytevector binary-output-port bytevector start n) 返回: 未指定 库: (rnrs io ports), (rnrs)

startn 必须是非负的精确整数,且 startn 的和一定不能超过 bytevector 的长度。如果不指定,则 start 默认为 0,而 n 默认为 bytevector 的长度与 start 的差。

这个过程把 bytevector 始于 startn 个字节写入端口,并把端口位置移动到写入字节的后面。

过程: (put-char textual-output-port char) 返回: 未指定 库: (rnrs io ports), (rnrs)

这个过程把 char 写入 textual-output-port, 并把端口位置前进一个节符。如果 textual-output-port 是一个经过编码转换的端口,则底层字节流中的位置可能后移多于一个字节。

过程: (put-string textual-output-port string) 过程: (put-string textual-output-port string start) 过程: (put-string textual-output-port string start n) 返回: 未指定 库: (rnrs io ports), (rnrs)

startn 必须是非负的精确整数,且 startn 的和一定不能超过 string 的长度。如果不指定,则 start 默认为 0,而 n 默认为 string 的长度与 start 的差。

这个过程把 string 始于 startn 个字符写入端口,并把端口位置移动到写入字符的后面。

过程: (put-datum textual-output-port obj) 返回: 未指定 库: (rnrs io ports), (rnrs)

这个过程把 obj 的外部表示形式写入 textual-output-port. 如果 obj 没有作为 datum 的外部表示形式,则过程的行为是未定义的。精确的外部表示形式是依赖于实现的,但是当 obj 确实有一个作为 datum 的外部表示形式时,则 put-datum 应当生成一个字符序列,此字符序列能够在之后被 get-datum 读取为一个与 obj 等价(基于 equal? )的对象。 put-datum, write, 和 display 的实现可参见 12.5 节。

过程: (flush-output-port output-port) 返回: 未指定 库: (rnrs io ports), (rnrs)

这个过程强制把缓冲区中与 output-port 相关联的任何字节或字符立即发送到底层的流。

7.9. 便捷 I/O

本节中的过程被称为“便捷” I/O 操作,因为它们为创建文本端口及与文本端口交互提供了一种稍微简化的接口。它们也提供了对 R5RS(不支持独立的二进制和文本 I/O)的向后兼容。

调用便捷输入/输出过程时,可以传入或不传入显式的端口实参。如果调用时不带有显式的端口实参,则酌情使用当前的输入或输出端口。例如, (read-char)(read-char (current-input-port)) 均返回当前输入端口的下一个字符。

过程: (open-input-file path) 返回: 一个新的输入端口 库: (rnrs io simple), (rnrs)

path 必须是命名了一个文件的字符串或其它某些依赖于实现的值。 open-input-filepath 命名的文件创建了一个新的文本输入端口,和以默认选项,一个与实现相关的缓冲模式,及一个与实现相关的编码转换器调用 open-file-input-port 一样。

以下代码在一个表达式中展示了 open-input-file, read, 以及 close-port 的用法,这个表达式从名为 "myfile.ss" 的文件中收集了一个对象列表。

(let ([p (open-input-file "myfile.ss")])
  (let f ([x (read p)])
    (if (eof-object? x)
        (begin
          (close-port p)
          '())
        (cons x (f (read p))))))

过程: (open-output-file path) 返回: 一个新的输出端口 库: (rnrs io simple), (rnrs)

path 必须是命名了一个文件的字符串或其它某些依赖于实现的值。 open-output-filepath 命名的文件创建了一个新的文本输出端口,和以默认选项,一个与实现相关的缓冲模式,及一个与实现相关的编码转换器调用 open-file-output-port 一样。

以下代码展示了如何使用 open-output-file 把一个对象列表( list-to-be-printed 的值)写入名为 "myfile.ss" 的文件中,对象之间以换行符分隔。

(let ([p (open-output-file "myfile.ss")])
  (let f ([ls list-to-be-printed])
    (if (not (null? ls))
        (begin
          (write (car ls) p)
          (newline p)
          (f (cdr ls)))))
  (close-port p))

过程: (call-with-input-file path procedure) 返回: procedure 的返回值 库: (rnrs io simple), (rnrs)

path 必须是命名了一个文件的字符串或其它某些依赖于实现的值。 procedure 应当接受一个参数。

call-with-input-filepath 命名的文件创建了一个新的输入端口,和 open-input-file 一样,并把这个端口传入 procedure. 如果 procedure 返回,则 call-with-input-file 关闭输入端口,并返回 procedure 的返回值。

如果一个创建于 procedure 之外的 continuation 被调用,则 call-with-input-file 不会自动关闭输入端口,因为之后可能会有另一个创建于 procedure 之内的 continuation 被调用,并把控制权返还给 procedure. 如果 procedure 没有返回,则只有在可以确认此输入端口不再可以被访问时,实现才可以自由地关闭此输入端口。如 5.6 节中所示,一个创建于 procedure 之外的 continuation 被调用时,可以使用 dynamic-wind 来确保端口被关闭。

以下例子在一个表达式中展示了 call-with-input-file 的用法,这个表达式从名为 "myfile.ss" 的文件中收集了一个对象列表。它在功能上等价于上文中为 open-input-file 给出的示例。

(call-with-input-file "myfile.ss"
  (lambda (p)
    (let f ([x (read p)])
      (if (eof-object? x)
          '()
          (cons x (f (read p)))))))

不带错误检查的 call-with-input-file 可以定义如下。

(define call-with-input-file
  (lambda (filename proc)
    (let ([p (open-input-file filename)])
      (let-values ([v* (proc p)])
        (close-port p)
        (apply values v*)))))

过程: (call-with-output-file path procedure) 返回: procedure 的返回值 库: (rnrs io simple), (rnrs)

path 必须是命名了一个文件的字符串或其它某些依赖于实现的值。 procedure 应当接受一个参数。

call-with-output-filepath 命名的文件创建了一个新的输出端口,和 open-output-file 一样,并把这个端口传入 procedure. 如果 procedure 返回,则 call-with-output-file 关闭输出端口,并返回 procedure 的返回值。

如果一个创建于 procedure 之外的 continuation 被调用,则 call-with-output-file 不会自动关闭输出端口,因为之后可能会有另一个创建于 procedure 之内的 continuation 被调用,并把控制权返还给 procedure. 如果 procedure 没有返回,则只有在可以确认此输出端口不再可以被访问时,实现才可以自由地关闭此输出端口。如 5.6 节中所示,一个创建于 procedure 之外的 continuation 被调用时,可以使用 dynamic-wind 来确保端口被关闭。

以下代码展示了如何使用 call-with-output-file 把一个对象列表( list-to-be-printed 的值)写入名为 "myfile.ss" 的文件中,对象之间以换行符分隔。它在功能上等价于上文中为 open-output-file 给出的示例。

(call-with-output-file "myfile.ss"
  (lambda (p)
    (let f ([ls list-to-be-printed])
      (unless (null? ls)
        (write (car ls) p)
        (newline p)
        (f (cdr ls))))))

不带错误检查的 call-with-output-file 可以定义如下。

(define call-with-output-file
  (lambda (filename proc)
    (let ([p (open-output-file filename)])
      (let-values ([v* (proc p)])
        (close-port p)
        (apply values v*)))))

过程: (with-input-from-file path thunk) 返回: thunk 的返回值 库: (rnrs io simple), (rnrs)

path 必须是命名了一个文件的字符串或其它某些依赖于实现的值。 thunk 必须是一个过程,且应当接受 0 个参数。

with-input-from-file 在应用 thunk 期间,把当前输入端口临时更改为打开 path 命名的文件的结果,如同调用 open-input-file. 如果 thunk 返回,则端口被关闭,并把当前输入端口恢复为它先前的值。

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

过程: (with-output-to-file path thunk) 返回: thunk 的返回值 库: (rnrs io simple), (rnrs)

path 必须是命名了一个文件的字符串或其它某些依赖于实现的值。 thunk 必须是一个过程,且应当接受 0 个参数。

with-output-to-file 在应用 thunk 期间,把当前输出端口临时更改为打开 path 命名的文件的结果,如同调用 open-output-file. 如果 thunk 返回,则端口被关闭,并把当前输出端口恢复为它先前的值。

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

过程: (read) 过程: (read textual-input-port) 返回: 一个 Scheme datum 对象,或 eof 对象 库: (rnrs io simple), (rnrs)

如果不提供 textual-input-port, 则默认为当前输入端口。其它情况下,这个过程等价于 get-datum.

过程: (read-char) 过程: (read-char textual-input-port) 返回: textual-input-port 中的下一个字符 库: (rnrs io simple), (rnrs)

如果不提供 textual-input-port, 则默认为当前输入端口。其它情况下,这个过程等价于 get-char.

过程: (peek-char) 过程: (peek-char textual-input-port) 返回: textual-input-port 中的下一个字符 库: (rnrs io simple), (rnrs)

如果不提供 textual-input-port, 则默认为当前输入端口。其它情况下,这个过程等价于 lookahead-char.

过程: (write obj) 过程: (write obj textual-output-port) 返回: 未指定 库: (rnrs io simple), (rnrs)

如果不提供 textual-output-port, 则默认为当前输出端口。其它情况下,这个过程等价于参数顺序相反的 put-datum. put-datum, write, 和 display 的实现可参见 12.5 节。

过程: (display obj) 过程: (display obj textual-output-port) 返回: 未指定 库: (rnrs io simple), (rnrs)

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

displaywriteput-datum 类似,但是直接打印在 obj 中出现的字符串和字符。字符串打印时不带有引号或特殊字符的转义符,和使用 put-string 时一样,而字符打印时不带有 #\ 标记,和使用 put-char 时一样。使用 display 时,三元素列表 (a b c) 和两元素列表 ("a b" c) 均打印为 (a b c). 因此, display 不应被用于打印将被 read 读取的对象。 display 主要用于打印消息,其中 obj 多为字符串。 put-datum, write, 和 display 的实现可参见 12.5 节。

过程: (write-char char) 过程: (write-char char textual-output-port) 返回: 未指定 库: (rnrs io simple), (rnrs)

如果不提供 textual-output-port, 则默认为当前输出端口。其它情况下,这个过程等价于参数顺序相反的 put-char.

过程: (newline) 过程: (newline textual-output-port) 返回: 未指定 库: (rnrs io simple), (rnrs)

如果不提供 textual-output-port, 则默认为当前输出端口。 newline 向端口发送一个换行字符。

过程: (close-input-port input-port) 过程: (close-output-port output-port) 返回: 未指定 库: (rnrs io simple), (rnrs)

close-input-port 关闭一个输入端口,而 close-output-port 关闭一个输出端口。提供这些过程是为了向后兼容 R5RS; 使用它们实际上并不比使用 close-port 更便捷。

7.10. 文件系统操作

对于和文件系统的交互,除了文件输入/输出,Scheme 还有两个标准操作: file-exists?delete-file. 大多数实现还支持更多的操作。

过程: (file-exists? path) 返回: 如果 path 指定的文件存在,则为 #t, 否则为 #f. 库: (rnrs files), (rnrs)

path 必须是命名了一个文件的字符串或其它某些依赖于实现的值。 file-exists? 是否指向符号链接是未定义的。

过程: (delete-file path) 返回: 未指定 库: (rnrs files), (rnrs)

path 必须是命名了一个文件的字符串或其它某些依赖于实现的值。如果 path 指定的文件存在,且可以被删除,则 delete-file 移除此文件,否则,它抛出一个条件类型的 &i/o-filename 异常。 delete-file 是否指向符号链接是未定义的。

7.11. 字节向量/字符串转换

本节中介绍的过程编码或解码字符序列,把字符串转换为字节向量,或把字节向量转换为字符串。虽然它们可能使用字节向量输入和输出端口来实现,但它们不一定要涉及输入/输出。

前两个过程, bytevector->stringstring->bytevector, 要传入一个显式的编码转换器实参,以决定字符编码,eol 风格,和错误处理模式。其它过程以隐含的 eol 风格 none 和错误处理模式 replace 执行特定的 Unicode 转换。

过程: (bytevector->string bytevector transcoder) 返回: 一个字符串,包含 bytevector 中编码的字符 库: (rnrs io ports), (rnrs)

这个操作,至少在效果上,以指定的 transcoder 创建了一个字节向量输入端口,从中读取所有可用的字符,如同使用 get-string-all, 并把这些字符放入输出的字符串中。

(let ([tx (make-transcoder (utf-8-codec) (eol-style lf)
            (error-handling-mode replace))])
  (bytevector->string #vu8(97 98 99) tx)) => "abc"

过程: (string->bytevector string transcoder) 返回: 一个字节向量,包含 string 中的字符编码 库: (rnrs io ports), (rnrs)

这个操作,至少在效果上,以指定的 transcoder 创建了一个字节向量输出端口,向其中写入字符串中的所有字符,然后提取一个包含所累积字节的字节向量。

(let ([tx (make-transcoder (utf-8-codec) (eol-style none)
            (error-handling-mode raise))])
  (string->bytevector "abc" tx)) => #vu8(97 98 99)

过程: (string->utf8 string) 返回: 一个字节向量,包含 string 的 UTF-8 编码 库: (rnrs bytevectors), (rnrs)

过程: (string->utf16 string) 过程: (string->utf16 string endianness) 过程: (string->utf32 string) 过程: (string->utf32 string endianness) 返回: 一个字节向量,包含 string 的指定编码 库: (rnrs bytevectors), (rnrs)

endianness 必须是符号 biglittle 之一。如果没有指定 endianness, 或为符号 big, 则 string->utf16 返回 string 的 UTF-16BE 编码,而 string->utf32 返回 string 的 UTF-32BE 编码。如果 endianness 为符号 little, 则 string->utf16 返回 string 的 UTF-16LE 编码,而 string->utf32 返回 string 的 UTF-32LE 编码。编码中不包含字节序标记。

过程: (utf8->string bytevector) 返回: 一个字符串,包含 bytevector 的 UTF-8 解码 库: (rnrs bytevectors), (rnrs)

过程: (utf16->string bytevector endianness) 过程: (utf16->string bytevector endianness endianness-mandatory?) 过程: (utf32->string bytevector endianness) 过程: (utf32->string bytevector endianness endianness-mandatory?) 返回: 一个字符串,包含 bytevector 的指定解码 库: (rnrs bytevectors), (rnrs)

endianness 必须是符号 biglittle 之一。这些过程返回 bytevector 的 UTF-16 或 UTF-32 解码,其表示形式的字节序由实参 endianness 或字节序标记(BOM)决定。如果 endianness-mandatory? 没有提供或为 #f, 则字节序由字节向量前面的 BOM 决定,如果没有 BOM,则由 endianness 决定。如果 endianness-mandatory?#t, 则字节序由 endianness 决定,并且,如果一个 BOM 出现在 bytevector 的前面,则它被视为一个普通的字符编码。

UTF-16 BOM 为指定 "big" 的双字节序列 #xFE,#xFF 或指定 "little" 的双字节序列 #xFF,#xFE. UTF-32 BOM 为指定 "big" 的四字节序列 #x00,#x00,#xFE,#xFF 或指定 "little" 的四字节序列 #xFF,#xFE,#x00,#x00.

R. Kent Dybvig / The Scheme Programming Language, Fourth Edition Copyright © 2009 The MIT Press. Electronically reproduced by permission. Illustrations © 2009 Jean-Pierre Hébert ISBN 978-0-262-51298-5 / LOC QA76.73.S34D93 to order this book / about this book

http://www.scheme.com

results matching ""

    No results matching ""