/
srfi-68-1.3.html
397 lines (374 loc) · 105 KB
/
srfi-68-1.3.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN""http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<body>
<head>
<title>SRFI 68: Comprehensive I/O</title>
</head>
<body>
<H1>Title</H1>
Comprehensive I/O
<H1>Authors</H1>
Michael Sperber
<H1>Status</H1>
This SRFI is currently in ``draft'' status. To see an explanation of each
status that a SRFI can hold, see
<A HREF="http://srfi.schemers.org/srfi-process.html">here</A>.
It will remain in draft status until 2005/06/13, or as amended. To
provide input on this SRFI, please <CODE>
<A HREF="mailto:srfi-68@srfi.schemers.org">mailto:srfi-68@srfi.schemers.org</A></CODE>.
See <A HREF="../../srfi-list-subscribe.html">instructions
here</A> to subscribe to the list. You can access previous messages via
<A HREF="http://srfi.schemers.org/srfi-68/mail-archive/maillist.html">the
archive of the mailing list</A>.
<P>
<ul>
<li>Received: <a href="http://srfi.schemers.org/srfi-68/srfi-68-1.1.html">2005/04/06</a></li>
<li>Draft: 2005/04/13 - 2005/06/13</li>
<li>Revised: <a href="http://srfi.schemers.org/srfi-68/srfi-68-1.2.html">2005/06/14</a></li>
<li>Revised: <a href="http://srfi.schemers.org/srfi-68/srfi-68-1.3.html">2005/07/27</a></li>
</ul>
<h1>Abstract</h1><p>This SRFI defines a comprehensive I/O subsystem for Scheme with three layers, where each layer is built on top of the one below it:</p><ul><li>The lowest, primitive layer provides unbuffered I/O, and is close to what a typical operating system offers.</li><li>The middle layer builds on lazy, mostly functional buffered streams.</li><li>The upper layer is similar in nature to the ports subsystem in R5RS, and provides conventional, imperative buffered input and output.</li></ul><p>The layer architecture is similar to the upper three layers of the I/O subsystem in <a href="http://www.standardml.org/Basis/">The Standard ML Basis Library</a>.</p><p>In particular, the subsystem provides</p><ul><li>buffered reading and writing</li><li>arbitrary lookahead at the streams level</li><li>dynamic redirection of input or output at the ports level</li><li>binary and text I/O, mixed if needed</li><li>translated data streams</li><li>unbuffered I/O at the primitive layer</li><li>the ability to create arbitrary I/O streams, such as to and from octet vectors and strings</li></ul><p>The subsystem does <em>not</em> provide</p><ul><li>formatted I/O</li><li>non-blocking or selective I/O</li><li>portable filenames, or any functionality for manipulating filenames</li><li>filesystem operations</li><li>socket I/O</li><li>extremely high-throughput or zero-copy I/O</li></ul><p>However, all of these could be added on top of one or several of the layers specified in this SRFI.</p><h1>Rationale</h1><p>The I/O subsystem in R5RS is too limited in many respects: It only provides for text I/O, it only allows reading at the character and the datum level, and some of the primitives are mis-designed or underspecified. As a result, almost every Scheme implementation has its own extensions to the I/O system, and rarely are two of these extensions compatible.</p><p>This SRFI is meant as a compelling replacement for the R5RS I/O subsystem. As such, it is a completely new design, and it is not based on the extensions a particular existing Scheme system provides. (In fact, it is probably, in its entirety, unlike what any existing Scheme system provides.) Moreover, it is meant to be a substrate for further extensions which can be built on top of the subsystem via the interface described here.</p><p>The design of this SRFI is driven by the requirements mentioned in the abstract on the one hand, and on the excellent design of the I/O subsystem in the Standard ML Basis Library. The latter is also the reason why this SRFI is different from the extensions provided by any existing Scheme implementation, as none of them picked up on the Basis design, and because the Basis design seems superior to the extensions I have looked at. (Among those I have looked at are Scheme 48, scsh, Gambit-C, and PLT Scheme.)</p><p>Note, however, that this SRFI differs from the SML Basis in several important respects, among them the handling of textual I/O streams, the ability to define translated streams, and the absence of any functionality related to non-blocking I/O. The latter is more properly in the domain of a thread/even system; Concurrent ML shows that the SML Basis plays well with such a system, and I expect the same to hold true here. The text encoding/translation functionality is different mainly because it plays to a different substrate for representing text (based on Unicode; see below) than Standard ML.</p><p>Like the Standard ML Basis I/O subsystem, the I/O system specified in this SRFI is probably not suitable for maximal-throughput I/O, chiefly because it does not re-use buffers, and because the buffer objects are octet vectors, with no further constraints on alignment or GC behavior. However, I deemed the achievable performance as more than adequate for most applications---it seemed a small price to pay for the convenient programming model.</p><h1>Specification</h1><h2>Prerequisites</h2><p>This SRFI is meant for Scheme implementations with the following prerequisites:</p><ul><li>support for SRFIs <a href="http://srfi.schemers.org/srfi-34/">34</a> (Exception Handling for Programs), <a href="http://srfi.schemers.org/srfi-35/">35</a> (Conditions), <a href="http://srfi.schemers.org/srfi-66/">66</a> (Octet Vectors)</li><li>Unicode support</li></ul><h2>Unicode support</h2><p>This SRFI assumes that the <code>char</code> datatype in Scheme to correspond to Unicode scalar values. This, in turn, means that strings are represented as vectors of scalar values. (Note that this is consistent with <a href="http://srfi.schemers.org/srfi-14/">SRFI 14</a> (Character-set library) and <a href="http://srfi.schemers.org/srfi-75/">SRFI 75</a> (R6RS Unicode data).) It may be possible to make this SRFI work in an ASCII- or Latin-1-only system, but I have not made any special provisions to ensure this.</p><h2>Filenames</h2><p>Some of the procedures described here accept a filename <var>filename</var> as an argument. Valid values for such a filename include strings naming a file using the native notation of the operating system the Scheme implementation happens to be running on.</p><p>It is expected that a future SRFI will extend this set of values by a more abstract representation: This is necessary, as the most common operating systems do not really use strings for representing filenames, but rather octet sequences. Moreover, the string notation is difficult to manipulate and not very portable.</p><h2>General remarks</h2><p>For procedures that have no "natural" return value, this SRFI often uses the sentence</p><p><em>The return values are unspecified.</em></p><p>This means that number of return values and the return values are unspecified. However, the number of return values is such that it is accepted by a continuation created by <code>begin</code>. Specifically, on Scheme implementations where continuations created by <code>begin</code> accept an arbitrary number of arguments (this includes most implementations), it is suggested that the procedure return zero return values.</p><h2>Organization</h2><p>The I/O subsystem consists of three layers. Each layer can function independently from those above it. Moreover, each layer can be used without referring directly to the ones below it. Therefore, a Scheme implementation with a module system should offer each layer as an independent module. Moreover, some data extensions are common to all three I/O layers---specifically, the common I/O condition types.</p><h3>Data extensions</h3><h4>Condition types</h4><p>The I/O conditition type hierarchy here is similar, but not identical to the one described in <a href="http://srfi.schemers.org/srfi-36/">36</a> (I/O Conditions).</p><p>The following list depicts the I/O condition hierarchy; more detailed explanations of the condition types follow.</p><ul><li><code>&error</code><br><ul><li><code>&i/o-error</code><br><ul><li><code>&i/o-read-error</code></li><li><code>&i/o-write-error</code></li><li><code>&i/o-closed-error</code></li><li><code>&i/o-invalid-position-error</code></li></ul><br><ul><li><code>&i/o-filename-error</code> (has a <code>filename</code> field)<br><ul><li><code>&i/o-malformed-filename-error</code></li><li><code>&i/o-file-protection-error</code><br><ul><li><code>&i/o-file-is-read-only-error</code></li></ul></li><li><code>&i/o-file-already-exists-error</code></li><li><code>&i/o-no-such-file-error</code></li></ul></li></ul></li></ul></li></ul><p>In exceptional situations not described as "it is an error", the procedures described in the specification below will raise an <code>&i/o-error</code> condition object. Except where explicitly specified, there is no guarantee that the raised condition object will contain all the information that would be applicable. It is recommended, however, that an implementation of this SRFI provide all information about an exceptional situation in the condition object that is available at the place where it is detected.</p><dl><dt><pre>(define-condition-type &i/o-error &error
i/o-error?)
</pre></dt><dd><p>This is a supertype for a set of more specific I/O errors.</p></dd><dt><p><pre>(define-condition-type &i/o-read-error &i/o-error
i/o-read-error?)
</pre></p></dt><dd><p>This condition type specifies a read error that occurred during an I/O operation.</p></dd><dt><p><pre>(define-condition-type &i/o-write-error &i/o-error
i/o-write-error?)
</pre></p></dt><dd><p>This condition type specifies a write error that occurred during an I/O operation.</p></dd><dt><p><pre>(define-condition-type &i/o-invalid-position-error &i/o-error
i/o-invalid-position-error?
(position i/o-error-position))
</pre></p></dt><dd><p>This condition type specifies that an attempt to set the file position specified an invalid position.</p></dd><dt><p><pre>(define-condition-type &i/o-closed-error &i/o-error
i/o-error?)
</pre></p></dt><dd><p>A condition of this type specifies that an operation tried to operate on a closed I/O object under the assumption that it is open.</p></dd><dt><p><pre>(define-condition-type &i/o-filename-error &i/o-error
i/o-filename-error?
(filename i/o-error-filename))
</pre></p></dt><dd><p>This condition type specifies an I/O error that occurred during an operation on a named file. Condition objects belonging to this type must specify a file name in the <code>filename</code> field.</p></dd><dt><p><pre>(define-condition-type &i/o-malformed-filename-error &i/o-filename-error
i/o-malformed-filename-error?)
</pre></p></dt><dd>This condition type indicates that a file name had an invalid format.</dd><dt><p><pre>(define-condition-type &i/o-operation-error &i/o-error
i/o-operation-error?
(operation i/o-error-operation))
</pre></p></dt><dd><p>This condition type specifies an I/O error that occurred during an specific operation. Condition objects belonging to this type must specify the procedure that was called to perform the operation in the <code>operation</code> field.</p></dd><dt><p><pre>(define-condition-type &i/o-operation-not-available-error &i/o-operation-error
i/o-operation-not-available-error?)
</pre></p></dt><dd>This condition type indicates that the program tried to perform an I/O operation that was not available.</dd><dt><p><pre>(define-condition-type &i/o-file-protection-error &i/o-filename-error
i/o-file-protection-error?)
</pre></p></dt><dd><p>A condition of this type specifies that an operation tried to operate on a named file with insufficient access rights.</p></dd><dt><p><pre>(define-condition-type &i/o-file-is-read-only-error &i/o-file-protection-error
i/o-file-is-read-only-error?)
</pre></p></dt><dd><p>A condition of this type specifies that an operation tried to operate on a named read-only file under the assumption that it is writeable.</p></dd><dt><p><pre>(define-condition-type &i/o-file-already-exists-error &i/o-filename-error
i/o-file-already-exists-error?)
</pre></p></dt><dd><p>A condition of this type specifies that an operation tried to operate on an existing named file under the assumption that it does not exist.</p></dd><dt><p><pre>(define-condition-type &i/o-file-exists-not-error &i/o-filename-error
i/o-file-exists-not-error?)
</pre></p></dt><dd><p>A condition of this type specifies that an operation tried to operate on an non-existent named file under the assumption that it exists.</p></dd></dl><h4>Buffer modes</h4><p>Each output stream and each output port has an associated <i>buffer mode</i> that defines when an output operation will flush the buffer associated with the output stream. The possible buffer modes are <code>none</code> for no buffering, <code>line</code> for flushing upon newlines, and <code>block</code> for block-based buffering.</p><dl><dt><code>(buffer-mode </code><var>name</var><code>)</code> (syntax)</dt><dd><p><var>Name</var> must be one of the identifiers <code>none</code>, <code>line</code>, and <code>block</code>. This returns a buffer-mode object denoting the associated buffer mode. There is only one such object for each mode, so a program can compare them using <code>eq?</code>.</p></dd><dt><code>(buffer-mode? </code><var>obj</var><code>)</code></dt><dd><p>This returns <code>#t</code> if the argument is a buffer-mode object, <code>#f</code> otherwise.</p></dd></dl><a name="file-options"><h4>File Options</h4></a><p>When opening a file for output, the various procedures in this SRFI accept a <i>file-options</i> object containing a set of flags that specify how the file is to be opened:</p><dl><dt><code>(file-options </code><var>file-options-name...</var><code>)</code> (syntax)</dt><dd><p>The syntax <code>file-options</code> returns a file-options object with the specified options set. The following options may be used:</p><table><tr><td><code>create</code></td><td>create file if it does not already exist</td></tr><tr><td><code>exclusive</code></td><td>an error will be raised if this option and <code>create</code> are both set and the file already exists</td></tr><tr><td><code>truncate</code></td><td>file is truncated</td></tr><tr><td><code>append</code></td><td>writes are appended to existing contents</td></tr></table><p>Any options not in this list may be platform-specific extensions. The implementation is free to ignore them, but must not signal an error.</p></dd><dt><code>(file-options-include? </code><var>file-options-1</var> <var>file-options-2</var><code>)</code></dt><dd><p>This returns true if <var>file-options-1</var> includes all of the flags listed in <var>file-options-2</var>, <code>#f</code> otherwise.</p></dd><dt><code>(file-options? </code><var>obj</var><code>)</code></dt><dd><p>This returns <code>#t</code> if the argument is a file-options object, <code>#f</code> otherwise.</p></dd><dt><code>(file-options-union </code><var>file-options</var> ...<code>)</code></dt><dd>This returns a file-options object containing all the flags of its arguments.</dd></dl><h3>Primitive I/O</h3><p>The Primitive I/O layer is an abstraction of the low-level I/O system calls commonly available on file descriptors: Streams and ports from the upper layers of the I/O system always perform access through the abstractions provided by this layer. The objects representing I/O descriptors are called <em>readers</em> for input and <em>writers</em> for output. They are unbuffered and operate purely on binary data.</p><p>This layer only specifies a fairly small set of operations --- a subset of the Standard ML Basis <code>PRIM_IO</code> signature. Specifically, all functionality related to non-blocking I/O or polling is missing here. This is intentional, as this functionality should be integrated with the threads system of the underlying implementation, and is thus outside the scope of this (already large) SRFI. Instead, it is expected that the set of operations available on primitive I/O readers and writers will be augmented by future specifications, as will be the available constructors for these objects.</p><p>The Primitive I/O layer introduces one condition type of its own.</p><dl><dt><p><pre>(define-condition-type &i/o-reader/writer-error &i/o-error
i/o-reader/writer-error?
(reader/writer i/o-error-reader/writer))
</pre></p></dt><dd><p>This condition type allows specifying the particular reader or writer with which an I/O error is associated. The <code>reader/writer</code> field has purely informational purpose. Conditions raised by Primitive I/O procedures <em>may</em> include an <code>&i/o-reader/writer-error</code> condition, but are not required to do so.</p></dd></dl><h4>I/O buffers</h4><dl><dt><code>(make-i/o-buffer </code><var>size</var><code>)</code></dt><dd><p>This creates a u8vector of size <var>size</var> with undefined contents. Callers of the Primitive I/O procedures are encouraged to use u8vectors created by <code>make-i/o-buffer</code> because they might have alignment and placement characteristics that make <code>reader-read!</code> and <code>writer-write!</code> more efficient. (These procedures are still required to work on regular u8vectors, however.)</p></dd></dl><h4>Readers</h4><p>A reader object typically stands for a file or device descriptor, but can also represent the output of some algorithm, such as in the case of string readers. The sequence of octets represented is potentially unbounded, and is punctuated by end of file elements.</p><dl><dt><code>(reader? </code><var>obj</var><code>)</code></dt><dd><p><p>Returns <code>#t</code> if <var>obj</var> is a reader, otherwise returns <code>#f</code>.</p></p></dd><dt><code>(make-simple-reader </code><var>id</var> <var>descriptor</var> <var>chunk-size</var> <var>read!</var> <var>available</var> <var>get-position</var> <var>set-position!</var> <var>end-position</var> <var>close</var><code>)</code></dt><dd><p>Returns a reader object. <var>Id</var> is a string naming the reader, provided for informational purposes only. For a file, this will be a string representation of the file name. <var>Descriptor</var> is supposed to be the low-level object connected to the reader, such as the OS-level file descriptor or the source string in the case of a string reader.</p><p><var>Chunk-size</var> must be a positive exact integer, and is the recommended efficient size of the read operations on this reader. This is typically the block size of the buffers of the operating system. Note that this is just a recommendation --- calls to the <var>read!</var> procedure (see below) will not necessarily use it. A value of 1 represents a recommendation to use unbuffered reads.</p><p>The remaining arguments are procedures --- <var>get-position</var>, <var>set-position!</var>, and <var>end-position</var> may be omitted, in which case the corresponding arguments must be <code>#f</code>.</p><dl><dt><code>(<var>read!</var> </code><var>octets</var> <var>start</var> <var>count</var><code>)</code></dt><dd><p><var>Start</var> and <var>count</var> must be non-negative exact integers. This reads up to <var>count</var> octets from the reader and writes them into <var>octets</var>, which must be a u8vector, starting at index <var>start</var>. <var>Octets</var> must have at least <var>start</var> + <var>count</var> elements. This procedure returns the number of octets read as an exact integer. It returns 0 if it encounters an end of file, or if <var>count</var> is 0. This procedure blocks until at least one octet has been read or it has encountered end of file.</p></dd><dt><code>(<var>available</var>)</code></dt><dd><p>This returns an estimate of the total number of available octets left in the stream. The return value is either an exact integer, or <code>#f</code> if no such estimate is possible. There is no guarantee that this estimate will have any specific relationship to the true number of available octets.</p></dd><dt><code>(<var>get-position</var>)</code></dt><dd><p>This procedure, when present, returns the current position in the octet stream as an exact integer counting the number of octets since the beginning of the stream.</p></dd><dt><code>(<var>set-position!</var> </code><var>pos</var><code>)</code></dt><dd><p>This procedure, when present, moves to position <var>pos</var> (which must be a non-negative exact integer) in the stream.</p></dd><dt><code>(<var>end-position</var>)</code></dt><dd><p>This procedure, when present, returns the position in the octet stream of the next end of file, without changing the current position.</p></dd><dt><code>(<var>close</var>)</code></dt><dd><p>This procedure marks the reader as closed, performs any necessary cleanup, and releases the resources associated with the reader. Further operations on the reader may signal an error.</p></dd></dl></dd><dt><code>(reader-id </code><var>reader</var><code>)</code></dt><dd><p>This returns the value of the <var>id</var> field of the argument reader.</p></dd><dt><code>(reader-descriptor </code><var>reader</var><code>)</code></dt><dd><p>This returns the value of the <var>descriptor</var> field of the argument reader.</p></dd><dt><code>(reader-chunk-size </code><var>reader</var><code>)</code></dt><dd><p>This returns the value of the <var>chunk-size</var> field of the argument reader.</p></dd><dt><code>(reader-read! </code><var>reader</var> <var>octets</var> <var>start</var> <var>count</var><code>)</code></dt><dd><p>This calls the <var>read!</var> procedure of <var>reader</var> with the remaining arguments.</p></dd><dt><code>(reader-available </code><var>reader</var><code>)</code></dt><dd><p>This calls the <var>available</var> procedure of <var>reader</var>.</p></dd><dt><code>(reader-has-get-position? </code><var>reader</var><code>)</code></dt><dd><p>This returns <code>#t</code> if <var>reader</var> has a <var>get-position</var> procedure, and <code>#f</code> otherwise.</p></dd><dt><code>(reader-get-position </code><var>reader</var><code>)</code></dt><dd><p>This calls the <var>get-position</var> procedure of <var>reader</var>, if present. It is an error to call this procedure if <var>reader</var> does not have a <var>get-position</var> procedure.</p></dd><dt><code>(reader-has-set-position!? </code><var>reader</var><code>)</code></dt><dd><p>This returns <code>#t</code> if <var>reader</var> has a <var>set-position!</var> procedure, and <code>#f</code> otherwise.</p></dd><dt><code>(reader-set-position! </code><var>reader</var> <var>pos</var><code>)</code></dt><dd><p>This calls the <var>set-position!</var> procedure of <var>reader</var> with the <var>pos</var> argument, if present. It is an error to call this procedure if <var>reader</var> does not have a <var>set-position!</var> procedure.</p></dd><dt><code>(reader-has-end-position? </code><var>reader</var><code>)</code></dt><dd><p>This returns <code>#t</code> if <var>reader</var> has a <var>end-position</var> procedure, and <code>#f</code> otherwise.</p></dd><dt><code>(reader-end-position </code><var>reader</var><code>)</code></dt><dd><p>This calls the <var>end-position</var> procedure of <var>reader</var>, if present. It is an error to call this procedure if <var>reader</var> does not have a <var>end-position</var> procedure.</p></dd><dt><code>(reader-close)</code></dt><dd><p>This calls the <var>close</var> procedure of <var>reader</var>.</p></dd><dt><code>(open-u8vector-reader </code><var>octets</var><code>)</code></dt><dd><p>This returns a reader that uses <var>octets</var>, a u8vector, as its contents. This reader has <var>get-position</var>, <var>set-position!</var>, and <var>end-position</var> operations.</p></dd><dt><code>(open-file-reader </code><var>filename</var><code>)</code></dt><dd><p>This returns a reader connected to the file named by <var>filename</var>.This reader may or may not have <var>get-position</var>, <var>set-position!</var>, and <var>end-position</var> operations.</p></dd><dt><code>(standard-input-reader)</code></dt><dd><p>This returns a reader connected to the standard input. The meaning of "standard input" is implementation-dependent.</p></dd></dl><h4>Writers</h4><p>A writer object typically stands for a file or device descriptor, but can also represent the sink for the output of some algorithm, such as in the case of string writers.</p><dl><dt><code>(make-simple-writer </code><var>id</var> <var>descriptor</var> <var>chunk-size</var> <var>write!</var> <var>get-position</var> <var>set-position!</var> <var>end-position</var> <var>close</var><code>)</code></dt><dd><p>Returns a writer object. <var>Id</var> is a string naming the writer, provided for informational purposes only. For a file, this will be a string representation of the file name. <var>Descriptor</var> is supposed to be the low-level object connected to the writer, such as the OS-level file descriptor.</p><p><var>Chunk-size</var> must be a positive exact integer, and is the recommended efficient size of the write operations on this writer. This is typically the block size of the buffers of the operating system. Note that this is just a recommendation --- calls to the <var>write!</var>procedure (see below) will not necessarily use it. A value of 1 represents a recommendation to use unbuffered writes.</p><p>The remaining arguments are procedures --- <var>get-position</var>, <var>set-position!</var>, and <var>end-position</var> may be omitted, in which case the corresponding arguments must be <code>#f</code>.</p><dl><dt><code>(<var>write!</var> </code><var>octets</var> <var>start</var> <var>count</var><code>)</code></dt><dd><p><var>Start</var> and <var>count</var> must be non-negative exact integers. This writes up to <var>count</var> octets in u8vector <var>octets</var> starting at index <var>start</var>. Before it does this, it will block until it can write at least one octet. It returns the number of octets actually written as a positive exact integer.</p></dd><dt><code>(<var>get-position</var>)</code></dt><dd><p>This procedure, when present, returns the current position in the octet stream as an exact integer counting the number of octets since the beginning of the stream.</p></dd><dt><code>(<var>set-position!</var> </code><var>pos</var><code>)</code></dt><dd><p>This procedure, when present, moves to position <var>pos</var> (which must be a non-negative exact integer) in the stream.</p></dd><dt><code>(<var>end-position</var>)</code></dt><dd><p>This procedure, when present, returns the position in the octet stream of the next end of file, without changing the current position.</p></dd><dt><code>(<var>close</var>)</code></dt><dd><p>This procedure marks the writer as closed, performs any necessary cleanup, and releases the resources associated with the writer. Further operations on the writer may signal an error.</p></dd></dl></dd><dt><code>(writer-id </code><var>writer</var><code>)</code></dt><dd><p>This returns the value of the <var>id</var> field of the argument writer.</p></dd><dt><code>(writer-descriptor </code><var>writer</var><code>)</code></dt><dd><p>This returns the value of the <var>descriptor</var> field of the argument writer.</p></dd><dt><code>(writer-chunk-size </code><var>writer</var><code>)</code></dt><dd><p>This returns the value of the <var>chunk-size</var> field of the argument writer.</p></dd><dt><code>(writer-write!! </code><var>writer</var> <var>octets</var> <var>start</var> <var>count</var><code>)</code></dt><dd><p>This calls the <var>write!</var> procedure of <var>writer</var> with the remaining arguments.</p></dd><dt><code>(writer-has-get-position? </code><var>writer</var><code>)</code></dt><dd><p>This returns <code>#t</code> if <var>writer</var> has a <var>get-position</var> procedure, and <code>#f</code> otherwise.</p></dd><dt><code>(writer-get-position </code><var>writer</var><code>)</code></dt><dd><p>This calls the <var>get-position</var> procedure of <var>writer</var>, if present. It is an error to call this procedure if <var>writer</var> does not have a <var>get-position</var> procedure.</p></dd><dt><code>(writer-has-set-position!? </code><var>writer</var><code>)</code></dt><dd><p>This returns <code>#t</code> if <var>writer</var> has a <var>set-position!</var> procedure, and <code>#f</code> otherwise.</p></dd><dt><code>(writer-set-position! </code><var>writer</var> <var>pos</var><code>)</code></dt><dd><p>This calls the <var>set-position!</var> procedure of <var>writer</var> with the <var>pos</var> argument, if present. It is an error to call this procedure if <var>writer</var> does not have a <var>set-position!</var> procedure.</p></dd><dt><code>(writer-has-end-position? </code><var>writer</var><code>)</code></dt><dd><p>This returns <code>#t</code> if <var>writer</var> has a <var>end-position</var> procedure, and <code>#f</code> otherwise.</p></dd><dt><code>(writer-end-position </code><var>writer</var><code>)</code></dt><dd><p>This calls the <var>end-position</var> procedure of <var>writer</var>, if present. It is an error to call this procedure if <var>writer</var> does not have a <var>end-position</var> procedure.</p></dd><dt><code>(writer-close)</code></dt><dd><p>This calls the <var>close</var> procedure of <var>writer</var>.</p></dd><dt><code>(open-u8vector-writer)</code></dt><dd><p>This returns a writer that can yield everything written to it as a octet vector. This writer has <var>get-position</var>, <var>set-position!</var>, and <var>end-position</var> operations.</p></dd><dt><code>(writer-u8vector </code><var>writer</var><code>)</code></dt><dd><p>The <var>writer</var> argument must be a u8vector writer returned by <code>open-u8vector-writer</code>. This procedure returns a newly allocated octet vector containing the data written to <var>writer</var> in sequence. Doing this in no way invalidates the writer or change its store.</p></dd><dt><code>(open-file-writer </code><var>filename</var> <var>file-options</var><code>)</code></dt><dd><p>This returns a writer connected to the file named by <var>filename</var>. The <var>file-options</var> object determines various aspects of the returned writer, see the <a href="#file-options">section on file options</a>. This writer may or may not have <var>get-position</var>, <var>set-position!</var>, and <var>end-position</var> operations. </p></dd><dt><code>(standard-output-writer)</code></dt><dd><p>This returns a writer connected to the standard output. The meaning of "standard output" is implementation-dependent.</p></dd><dt><code>(standard-error-writer)</code></dt><dd><p>This returns a writer connected to the standard error. The meaning of "standard error" is implementation-dependent.</p></dd></dl><h4>Opening files for reading and writing</h4><dl><dt><code>(open-file-reader+writer </code><var>filename</var> <var>file-options</var><code>)</code></dt><dd><p>This returns a reader and a writer connected to the file named by <var>filename</var>. The <var>file-options</var> object determines various aspects of the returned writer, see the <a href="#file-options">section on file options</a>. This writer and the writer may or may not have <var>get-position</var>, <var>set-position!</var>, and <var>end-position</var> operations. </p></dd></dl><h3>Stream I/O</h3><p>The Stream I/O layer defines high-level I/O operations on two new datatypes: <i>input streams</i> and <i>output streams</i>. These operations include binary and textual I/O. Input streams are treated in lazy functional style: input from a stream <var>s</var> yields an object representing the input itself, and a new input stream <var>s1</var>. <var>S</var> will continue to represent exactly the same position within the input; to advance within the stream, the program needs to perform input from <var>s1</var>. Consequently, input streams allow arbitrary lookahead, which is especially convenient for all kinds of scanning.</p><p>Output streams are more conventional, as the lazy functional style does not make sense with output.</p><p>Both input streams and output streams are either directly connected to underlying readers and writers, or are defined by translation on an underlying stream. This makes it possible to perform trivial transformations such as CR/LF translation, but also transparent recoding on the streams.</p><p>Textual I/O always uses UTF-8 as the underlying encoding. Other encodings can easily be supported by translating to or from UTF-8 using the translation framework. If a decoding error occurs, the implicit decoder will skip the octet starting the character encoding, yield a ? character, and attempt to continue decoding after that initial octet.</p><p>The Stream I/O layer adds an additional condition type:</p><pre>(define-condition-type &i/o-stream-error &i/o-error
i/o-stream-error?
(stream i/o-error-stream))
</pre><p>The <code>stream</code> field has purely informational purpose. Conditions raised in by Stream I/O procedures <em>may</em> include an <code>&i/o-stream-error</code> condition, but are not required to do so.</p><h4>Input streams</h4><p>Input streams come in two flavors: either directly based on a reader, or based on another input stream via translation. Input streams are in one of three states: active, truncated, or closed. When initially created, a stream is active. A program can retrieve the reader underlying an input stream---this automatically incurs disconnecting the stream from the reader, and puts the stream into the truncated state. When explicitly closed, the reader underlying an open input stream is closed as well. The closed state implies the truncated state.</p><p>Reading from a truncated stream is not an error; after all the existing buffers having been exhausted, the stream behaves as if an infinite sequence of end of files followed.</p><dl><dt><code>(input-stream? </code><var>obj</var><code>)</code></dt><dd><p>This returns <code>#t</code> if the argument is an input stream, <code>#f</code> otherwise.</p></dd><dt><a name="input-u8vector-some"><code>(input-u8vector-some </code><var>input-stream</var><code>)</code></a></dt><dd><p>This returns two values: a value and another input stream. The input stream returned points exactly past the data read. If any data is available before the next end of file, this returns a freshly allocated u8vector of non-zero size containing that data. If an end of file has been reached, the value is <code>#f</code>, and the input stream returned points just past the end of file. This procedure will block until either data is available or end of file is reached.</p></dd><dt><a name="input-u8"><code>(input-u8 </code><var>input-stream</var><code>)</code></a></dt><dd><p>This returns two values: a value and another input stream. The input stream returned points exactly past the data read. If a octet is available before the next end of file, this returns that octet as an exact integer. If an end of file has been reached, the value is <code>#f</code>, and the input stream returned points just past the end of file. This procedure will block until either data is available or end of file is reached.</p></dd><dt><a name="input-u8vector-n"><code>(input-u8vector-n </code><var>input-stream</var> <var>n</var><code>)</code></a></dt><dd><p><var>N</var> must be an exact, non-negative integer, specifying the number of octets to be read.This returns two values: a value and another input stream. It tries to read <var>n</var> octets. If <var>n</var> or more octets are available before the next end of file, it returns a u8vector of size <var>n</var>. If fewer octets are available before the next end of file, it returns a u8vector containing those octets. The input stream returned points exactly past the data read. If end of file has been reached, the return value is <code>#f</code>, and the input stream returned points just past the end of file. This procedure will block until either data is available or end of file is reached.</p></dd><dt><a name="input-u8vector-n!"><code>(input-u8vector-n! </code><var>input-stream</var> <var>octets</var> <var>start</var> <var>count</var><code>)</code></a></dt><dd><p><var>Count</var> must be an exact, non-negative integer, specifying the number of octets to be read. <var>Octets</var> must be a u8vector with at least <code>(+ <var>start</var> <var>count</var>)</code> elements. This returns two values: a value and another input stream. It tries to read <var>count</var> octets. If <var>count</var> or more octets are available before the next end of file, they are written into <var>octets</var> starting at index <var>start</var>, and it returns <var>count</var> as the value. If fewer octets are available before the next end of file, it writes the available octets into <var>octets</var> starting at index <var>start</var>, and it returns the number of octets actually read as the value. The input stream returned points exactly past the data read. If end of file has been reached, the return value is <code>#f</code>, and the input stream returned points just past the end of file. This procedure will block until either data is available or end of file is reached.</p></dd><dt><a name="input-u8vector-all"><code>(input-u8vector-all </code><var>input-stream</var><code>)</code></a></dt><dd><p>This returns two values: a value and another input stream. If data is available before the next end of file, the value is a u8vector containing all octets until that end of file. If not, the value is <code>#f</code>. The input stream returned points just past the end of file. Note that this function may block indefinitely on streams connected to interactive readers, even though data is available.</p></dd><dt><a name="input-string"><code>(input-string </code><var>input-stream</var><code>)</code></a></dt><dd><p>This returns two values: a value and another input stream. The input stream returned points exactly past the data read. If any data representing a string is available before the next end of file, this returns a string of non-zero size containing the UTF-8 decoding of that data as the first return value. If an end of file has been reached, it returns <code>#f</code>, and the input stream returned points just past the end of file. This procedure will block until either data is available or end of file is reached.</p></dd><dt><a name="input-char"><code>(input-char </code><var>input-stream</var><code>)</code></a></dt><dd><p>This returns two values: a value and another input stream. The input stream returned points exactly past the data read. If a char is available before the next end of file, this returns that char. If an end of file has been reached, the value is <code>#f</code>, and the input stream returned points just past the end of file. This procedure will block until either data is available or end of file is reached.</p></dd><dt><a name="input-string-n"><code>(input-string-n </code><var>input-stream</var> <var>n</var><code>)</code></a></dt><dd><p><var>N</var> must be an exact, non-negative integer, specifying the number of chars to be read.This returns two values: a value and another input stream. The input stream returned points exactly past the data read. It tries to read <var>n</var> chars. If <var>n</var> or more chars are available before the next end of file, it returns a string of size <var>n</var>consisting of those chars. If fewer chars are available before the next end of file, it returns a string containing those chars. If end of file has been reached, it returns <code>#f</code>, and the input stream returned points just past the end of file. This procedure will block until either data is available or end of file is reached.</p></dd><dt><a name="input-string-n!"><code>(input-string-n! </code><var>input-stream</var> <var>string</var> <var>start</var> <var>count</var><code>)</code></a></dt><dd><p><var>Count</var> must be an exact, non-negative integer, specifying the number of chars to be read.This returns two values: a value and another input stream. The input stream returned points exactly past the data read. It tries to read <var>count</var> chars. If <var>count</var> or more chars are available before the next end of file, they are written into <var>string</var> starting at index <var>start</var>, and it returns <var>count</var> as the value. If fewer chars are available before the next end of file, it writes the available chars into <var>string</var> starting at index <var>start</var>, and it returns the number of chars actually read as the value. If end of file has been reached, it returns <code>#f</code>, and the input stream returned points just past the end of file. This procedure will block until either data is available or end of file is reached.</p></dd><dt><a name="input-string-all"><code>(input-string-all </code><var>input-stream</var><code>)</code></a></dt><dd><p>This returns two values: a value and another input stream. If data is available before the next end of file, the value returned is a string contains all text until the next end of file. If no data is available, the value is <code>#f</code>. The input stream returned points just past the end of file. Note that this function may block indefinitely on streams connected to interactive readers, even though data is available.</p></dd><dt><a name="input-line"><code>(input-line </code><var>input-stream</var><code>)</code></a></dt><dd><p>This returns two values: a value and another input stream. If data is available before the next newline char, the value is a string that contains all text until the newline char. The input stream returned points just past the newline char.If end of file has been reached, the value is <code>#f</code>.</p></dd><dt><a name="stream-eof?"><code>(stream-eof? </code><var>input-stream</var><code>)</code></a></dt><dd><p>This returns <var>#t</var> if the stream has reached end of file, <var>#f</var> otherwise.</p></dd><dt><a name="input-stream-position"><code>(input-stream-position </code><var>input-stream</var><code>)</code></a></dt><dd><p>For reader-based input streams, this returns the reader position corresponding to the next octet read from the input stream. This procedure raises an <code>&i/o-operation-not-available-error</code> condition if the stream does not support the operation. It is an error to apply this procedure to a truncated or closed stream, or to a translated stream.</p></dd><dt><code>(input-stream-reader+constructor </code><var>input-stream</var><code>)</code></dt><dd><p><var>Input-stream</var> must be an open input stream. This returns two values: a reader and a procedure of one argument. The reader is the underlying reader of the stream at the end of the chain of translations whose head is <var>input-stream</var>. The procedure consumes a reader as its argument and returns a fresh input stream with the same chain of translations as <var>input-stream</var>. This also disconnects the input stream from the reader and puts it into the truncated state; all other input streams based on the input stream at the end of the translation chain (directly or indirectly) are also put into the truncated state.</p></dd><dt><a name="close-input-stream"><code>(close-input-stream </code><var>input-stream</var><code>)</code></a></dt><dd><p>This closes the underlying reader if <var>input-stream</var> is still open, and marks the input stream as closed. Applying <code>close-input-stream</code> to a closed stream has no effect. Closing an input stream also closes all input streams that are translations (directly or indirectly) of the input stream at the end of its own translation chain. The return values are unspecified.</p></dd><dt><code>(open-file-input-stream </code><var>filename</var><code>)</code></dt><dd><p>This opens a reader connected to the file named by <var>filename</var> and returns an input stream connected to it.</p></dd><dt><code>(open-u8vector-input-stream </code><var>octets</var><code>)</code></dt><dd><p>This opens a u8vector reader connected to <var>octets</var> and returns an input stream connected to it.</p></dd><dt><code>(open-string-input-stream </code><var>string</var><code>)</code></dt><dd><p>This opens a u8vector reader connected to the UTF-8 encoding of <var>string</var> and returns an input stream connected to it.</p></dd><dt><code>(open-reader-input-stream </code><var>reader</var><code>)</code></dt><dd><p>This returns an input stream connected to the reader <var>reader</var>.</p></dd><dt><code>(call-with-input-stream </code><var>input-stream</var> <var>proc</var><code>)</code></dt><dd><p>This calls <var>proc</var> with <var>input-stream</var> as an argument. If <var>proc</var> returns, then the stream is closed automatically and the values returned by <var>proc</var> are returned. If <var>proc</var> does not return, then the stream will not be closed automatically, unless it is possible to provide that the stream will never again be used for a read operation.</p></dd><dt><a name="make-translated-input-stream"><code>(make-translated-input-stream </code><var>input-stream</var> <var>translate-proc</var><code>)</code></a></dt><dd><p>This returns a translated input stream based on <var>input-stream</var>. <var>Translate-proc</var> must be a procedure that adheres to the following specification:</p><dl><dt><code>(<var>translate-proc</var> </code><var>input-stream</var> <var>wish</var><code>)</code></dt><dd><p><var>Input-stream</var> is the underlying input stream originally passed to <code>make-translated-input-stream</code>. <var>Wish</var> is either <code>#f</code>, <code>#t</code>, or an exact, non-negative integer, giving a hint how much data is requested. <code>#f</code> means a chunk of arbitrary size, suggesting that the user program called <code>input-u8vector-some</code>, <code>#t</code> means as much as possible, suggesting that the user program called <code>user-input-all</code>, and an integer specifies the requested number of octets. Note that <var>translate-proc</var> can ignore <var>wish</var>. The procedure must return two values, a u8vector, and another input stream, analogous to the various <code>input-...</code> procedures. <code>#f</code> denotes an end of file. The returned input stream points just past the data returned.</p></dd></dl></dd><dt><code>(standard-input-stream)</code></dt><dd><p>Return a freshly created stream connected to the standard input reader. Note that a program should not keep the returned stream live, as standard input read in other parts of the program accumulate buffer space.</p></dd></dl><h4>Output streams</h4><p>Output streams, like input streams, come in two flavors: either directly based on a writer, or based on another output stream via translation. </p><p>Output streams are in one of three states: active, terminated, or closed. When initially created, a stream is active. A program can retrieve the writer underlying an output stream---this automatically incurs disconnecting the stream from the writer, and puts the stream into the terminated state. When explicitly closed, the writer underlying an an output stream enters the closed state. The closed state implies the terminated state.</p><p>It is an error to perform an output operations on a terminated stream.</p><dl><dt><code>(output-stream? </code><var>obj</var><code>)</code></dt><dd><p>This returns <code>#t</code> if the argument is an output stream, <code>#f</code> otherwise.</p></dd><dt><a name="output-u8vector"><code>(output-u8vector </code><var>output-stream</var> <var>octets</var> <var>start</var> <var>count</var><code>)</code></a></dt><dt><code>(output-u8vector </code><var>output-stream</var> <var>octets</var> <var>start</var><code>)</code></dt><dt><code>(output-u8vector </code><var>output-stream</var> <var>octets</var><code>)</code></dt><dd><p><var>Start</var> and <var>count</var> must be non-negative exact integers that default to 0 and <code>(- (u8vector-length <var>octets</var>) <var>start</var>)</code>, respectively.This writes the <var>count</var> octets in u8vector <var>octets</var> starting at index <var>start</var> to the output stream. It is an error if the octet vector actually has size less than <var>start</var> + <var>count</var>. The return values are unspecified.</p></dd><dt><a name="output-u8"><code>(output-u8 </code><var>output-stream</var> <var>octet</var><code>)</code></a></dt><dd><p>This writes the octet <var>octet</var> (which must be an exact integer in the range [0,255]) to the stream. The return values are unspecified.</p></dd><dt><a name="output-char"><code>(output-char </code><var>output-stream</var> <var>char</var><code>)</code></a></dt><dd><p>This writes the UTF-8 encoding of the char <var>char</var> to the stream. The return values are unspecified.</p></dd><dt><a name="output-string"><code>(output-string </code><var>output-stream</var> <var>string</var> <var>start</var> <var>count</var><code>)</code></a></dt><dt><code>(output-string </code><var>output-stream</var> <var>string</var> <var>start</var><code>)</code></dt><dt><code>(output-string </code><var>output-stream</var> <var>string</var><code>)</code></dt><dd><p><var>Start</var> and <var>count</var> must be non-negative exact integers that default to 0 and <code>(- (string-length <var>octets</var>) <var>start</var>)</code>, respectively. This writes the UTF-8 encoding of the substring <code>(substring <var>string</var> (+ <var>start</var> <var>count</var>))</code> to the stream. The return values are unspecified.</p></dd><dt><a name="flush-output-stream"><code>(flush-output-stream </code><var>output-stream</var><code>)</code></a></dt><dd><p>This flushes any output from the buffer of <var>output-stream</var> to the underlying writer. It is a no-op if <var>output-stream</var> is terminated. The return values are unspecified.</p></dd><dt><a name="output-stream-position"><code>(output-stream-position </code><var>output-stream</var><code>)</code></a></dt><dd><p>For writer-based output streams, this returns the writer position corresponding to the next octet read from the output stream. This procedure raises an <code>&i/o-operation-not-available-error</code> condition if the stream does not support the operation. It is an error to apply this procedure to a terminated or closed stream, or to a translated stream.</p></dd><dt><a name="set-output-stream-position!"><code>(set-output-stream-position! </code><var>output-stream</var> <var>pos</var><code>)</code></a></dt><dd><p><var>Pos</var> must be a non-negative exact integer. This flushes the output stream and sets the current position of underlying writer to <var>pos</var>. This procedure raises an <code>&i/o-operation-not-available-error</code> condition if the stream does not support the operation. It is an error to apply this procedure to a terminated or closed stream, or to a translated stream. The return values are unspecified.</p></dd><dt><code>(output-stream-writer+constructor </code><var>output-stream</var><code>)</code></dt><dd><p><var>Output-stream</var> must be an open output stream. This returns two values: a writer and a procedure of one argument. The writer is the underlying writer of the stream at the end of the chain of translations whose head is <var>output-stream</var>. The procedure consumes a writer as its argument and returns a fresh output stream with the same chain of translations as <var>output-stream</var>, where each translation is in the same state as in the chain. This also disconnects the output stream from the writer and puts it into the terminated state; all other output streams based on the output stream at the end of the translation chain (directly or indirectly) are also put into the truncated state.</p></dd><dt><a name="close-output-stream"><code>(close-output-stream </code><var>output-stream</var><code>)</code></a></dt><dd><p>This closes the underlying writer if <var>output-stream</var> is still open, and marks the output stream as closed. Applying <code>close-output-stream</code> to a closed stream has no effect. Closing an output stream also closes all output streams that are translations (directly or indirectly) of the output stream at the end of its own translation chain. The return values are unspecified.</p></dd><dt><a name="output-stream-buffer-mode"><code>(output-stream-buffer-mode </code><var>output-stream</var><code>)</code></a></dt><dd><p>This returns the buffer-mode object of <var>output-stream</var>.</p></dd><dt><a name="set-output-stream-buffer-mode!"><code>(set-output-stream-buffer-mode! </code><var>output-stream</var> <var>buffer-mode</var><code>)</code></a></dt><dd><p>If the current buffer mode of <var>output-stream</var> is something other than <code>none</code> and <var>buffer-mode</var> is the <code>none</code> buffer-mode object, this will first flush the output stream. Then, it sets the buffer-mode object associated with <var>output-stream</var> to <var>buffer-mode</var>. The return values are unspecified.</p></dd><dt><code>(open-file-output-stream </code><var>filename</var> <var>file-options</var><code>)</code></dt><dd><p>This opens a writer connected to the file named by <var>filename</var> via <code>open-file-writer</code> (passing it <var>file-options</var>) and returns an output stream with unspecified buffering mode connected to it. </p></dd><dt><code>(open-writer-output-stream </code><var>writer</var> <var>buffer-mode</var><code>)</code></dt><dd><p>This returns an output stream connected to the writer <var>writer</var> with buffering according to <var>buffer-mode</var>.</p></dd><dt><code>(call-with-u8vector-output-stream </code><var>proc</var><code>)</code></dt><dd><p><var>Proc</var> is a procedure accepting one argument. This creates an unbuffered output stream connected to a u8vector writer, and calls <var>proc</var> with that output stream as an argument. The call to <code>call-with-u8vector-output-stream</code> returns the u8vector associated with the stream when <var>proc</var> returns.</p></dd><dt><code>(call-with-string-output-stream </code><var>proc</var><code>)</code></dt><dd><p><var>Proc</var> is a procedure accepting one argument. This creates an unbuffered output stream connected to a u8vector writer, and calls <var>proc</var> with that output stream as an argument. The call to <code>call-with-string-output-stream</code> returns the UTF-8 decoding of the octet vector associated with the stream when <var>proc</var> returns.</p></dd><dt><code>(call-with-output-stream </code><var>output-stream</var> <var>proc</var><code>)</code></dt><dd><p>This calls <var>proc</var> with <var>output-stream</var> as an argument. If <var>proc</var> returns, then the stream is closed automatically and the values returned by <var>proc</var> are returned. If <var>proc</var> does not return, then the stream will not be closed automatically, unless it is possible to provide that the stream will never again be used for a write operation.</p></dd><dt><a name="make-translated-output-stream"><code>(make-translated-output-stream </code><var>output-stream</var> <var>translate-proc</var> <var>state</var><code>)</code></a></dt><dd><p>This returns a translated output stream based on <var>output-stream</var>. The translation can thread an arbitrary state from one output operation to the next; the initial state is given by <var>state</var>. <var>Translate-proc</var> must be a procedure that adheres to the following specification:</p><dl><dt><code>(<var>translate-proc</var> </code><var>output-stream</var> <var>state</var> <var>data</var> <var>start</var> <var>count</var><code>)</code></dt><dd><p>This is expected to write the output data in <var>data</var> to <var>output-stream</var>, which is the output stream passed into <code>make-translated-output-stream</code>. <var>State</var> is the translation state associated with the output stream.<var>Data</var> is the data to be written: it is either <code>#f</code>, a u8vector or an octet represented an an exact integer. If <var>data</var> is a u8vector, <var>start</var> is an exact integer representing the starting index of the data to be written within <var>data</var>. <var>Count</var> is the number of data octets within <var>data</var> to be written. (Otherwise, the values of <var>start</var> and <var>count</var> are unspecified.</p><p>If <var>data</var> is <code>#f</code>, this means that the stream is being flushed, and the translation procedure should write out any remaining data encoded in <var>state</var> to the output-stream, and possiblye synchronize the protocol.</p><p>The procedure must return a new state object, which will be passed to the next call to <var>translate-proc</var>. It is recommended that <var>translate-proc</var> not modify <var>state</var> itself, but rather generate a new state object if necessary. Otherwise, the constructor procedure by <code>output-stream-writer+constructor</code> may not operate correctly.</p></dd></dl></dd><dt><code>(standard-output-stream)</code></dt><dt><code>(standard-error-stream)</code></dt><dd><p>This returns output streams on the standard output writer and standard error writer, respectively.</p></dd></dl><h4>Opening files for reading and writing</h4><dl><dt><code>(open-file-input+output-streams </code><var>filename</var> <var>file-options</var><code>)</code></dt><dd><p>This opens a reader and a writer connected to the file named by <var>filename</var> via <code>open-file-reader+writer</code> (passing it <var>file-options</var>) and returns an input stream and an output stream with unspecified buffering mode connected to them.</p></dd></dl><h3>Text Transcoding</h3><p>This part of the SRFI provides pre-packaged functionality for encoding and decoding text in some common encodings. A <i>transcoder</i> is an opaque object encapsulating a specific text encoding. This SRFI specifies how to obtain a transcoder given a text encoder/decoder (or <i>codec</i> for short) and a specified newline encoding. Codecs are constructed by pairing up input and output stream translators.</p><dl><dt><code>(transcoder </code><code>(codec <var>codec</var>)</code> <code>(eol-style <var>eol-style</var>)</code><code>)</code> (syntax)</dt><dd><p>This constructs a transcoder object from a specified codec and a specified end-of-line style. The <code>codec</code> and the <code>eol-style</code> clauses are both optional. If present, <var>codec</var> and <var>eol-style</var>, must be expressions that evaluate to a codec and an eol-style object, respectively. If not present, the codec defaults to "no codec" (corresponding to UTF-8), and the eol-style object defaults to the platform's standard EOL convention.</p><p>Any operands to a <code>transcoder</code> form that do not match the above syntax may be platform-specific extensions. The implementation is free to ignore them, but must not signal an error.</p></dd><dt><code>(update-transcoder </code><var>old</var> <code>(codec <var>codec</var>)</code> <code>(eol-style <var>eol-style</var>)</code><code>)</code> (syntax)</dt><dd><p>This form returns a new transcoder object constructed from an old one, with the <code>codec</code> and <code>eol-style</code> fields replaced by the specified values. (Again, the <code>codec</code> and the <code>eol-style</code> clauses are both optional. Also, unrecognized operands can be ignored, but cannot signal an error.)</p></dd><dt><code>(transcode-input-stream </code><var>input-stream</var> <var>transcoder</var><code>)</code></dt><dd><p>This creates a transcoded input stream from <var>input-stream</var>, assuming <var>input-stream</var> has the encoding specified by <var>transcoder</var>. It will translate the data from <var>input-stream</var> into UTF-8 with end-of-line encoded by U+000A.</p></dd><dt><code>(transcode-output-stream </code><var>output-stream</var> <var>transcoder</var><code>)</code></dt><dd><p>This creates a transcoded output stream from <var>output-stream</var>, translating the data fed into <var>output-stream</var> into the encoding specified by <var>transcoder</var>, assuming it is encoded as UTF-8 with end-of-line encoded by U+000A.</p></dd><dt><code>(eol-style </code><code>lf</code><code>)</code> (syntax)</dt><dt><code>(eol-style </code><code>crlf</code><code>)</code> (syntax)</dt><dt><code>(eol-style </code><code>cr</code><code>)</code> (syntax)</dt><dd><p>These forms evaluate to end-of-line-style objects - <code>lf</code> stands for using U+000A, <code>crlf</code> stands for using U+000D U+000A, and <code>cr</code> stands for using U+000D as end-of-line.</p></dd><dt><code>(make-codec </code><var>string</var> <var>translate-input</var> <var>translate-output</var> <var>initial-state</var><code>)</code></dt><dd><p>This constructs a codec object. <var>String</var> must be a string naming the encoding. <var>Translate-input</var> must be a translation procedure suitable for use by <a href="#make-translated-input-stream"><code>make-translated-input-stream</code></a>. <var>Translate-output</var> must be a translation procedure suitable for use by <a href="#make-translated-output-stream"><code>make-translated-output-stream</code></a>, and <var>initial-state</var> must be a suitable initial state.</p></dd><dt><code>latin-1-codec</code></dt><dt><code>utf-16le-codec</code></dt><dt><code>utf-16be-codec</code></dt><dt><code>utf-32le-codec</code></dt><dt><code>utf-32be-codec</code></dt></dl><dd><p>These are predefined codecs for the ISO8859-1, UTF-16LE, UTF-16BE, UTF32-LE, and UTF-32BE encodings.</p></dd><h3>Imperative I/O</h3><p>The Imperative I/O layer provides buffered I/O based on <i>ports</i>. Ports, like streams, allow buffered I/O on the underlying data sources and destinations. The output port abstractions are very similar to the output stream abstractions. However, unlike input streams, input ports are imperative; a read operation destructively removes data from the port. The port layer is very similar, but not identical, to the R5RS I/O system.</p><p>It is possible to construct ports from streams. Such a <code>stream port</code> is just a reference cell to a stream. The various procedures constructing ports described in this section are allowed but not required to return stream ports, however; the following section describes the abstractions specific to stream ports.</p><p>The Imperative I/O layer introduces one condition type of its own.</p><dl><dt><p><pre>(define-condition-type &i/o-port-error &i/o-error
i/o-port-error?
(port i/o-error-port))
</pre></p></dt><dd><p>This condition type allows specifying with what particular port an I/O error is associated. The <code>port</code> field has purely informational purpose. Conditions raised in by Imperative I/O procedures <em>may</em> include an <code>&i/o-port-error</code> condition, but are not required to do so.</p></dd></dl><h4>Input ports</h4><dl><dt><code>(input-port? </code><var>obj</var><code>)</code></dt><dd><p>This returns <code>#t</code> if the argument is an input port, <code>#f</code> otherwise.</p></dd><dt><code>(read-u8vector-some </code><var>input-port</var><code>)</code></dt><dd><p>If any data is available in <var>input-port</var> before the next end of file, this returns a freshly allocated u8vector of non-zero size containing that data, and updates <var>input-port</var> to point exactly past the data read. If an end of file has been reached, it returns <code>#f</code>, and the input stream is updated to point just past the end of file.</p><p>For a stream input port, this calls <a href="#input-u8vector-some"><code>input-u8vector-some</code></a> on the underlying input stream, updates the underlying input stream to the second return value, and returns <code>input-u8vector-some</code>'s first return value.</p></dd><dt><code>(read-u8 </code><var>input-port</var><code>)</code></dt><dd><p>If a octet is available before the next end of file, this returns that octet as an exact integer, and updates <var>input-port</var> to point exactly past the octet read. If an end of file has been reached, it returns <code>#f</code>, and the input stream is updated to point just past the end of file.</p><p>For a stream input port, this calls <a href="#input-u8"><code>input-u8</code></a> on the underlying input stream, updates the underlying input stream to the second return value, and returns <code>input-u8</code>'s first return value.</p></dd><dt><code>(read-u8vector-n </code><var>input-port</var> <var>n</var><code>)</code></dt><dd><p><var>N</var> must be an exact, non-negative integer, specifying the number of octets to be read.This tries to read <var>n</var> octets. If <var>n</var> or more octets are available before the next end of file, it returns a u8vector of size <var>n</var>. If fewer octets are available before the next end of file, it returns a u8vector containing those octets. Subsequently, the input stream is updated to point exactly past the data read. If end of file has been reached, this returns <code>#f</code>, and the input stream is updated to point just past the end of file.</p><p>For a stream input port, this calls <a href="#input-u8vector-n"><code>input-u8vector-n</code></a> on the underlying input stream, updates the underlying input stream to the second return value, and returns <code>input-u8vector-n</code>'s first return value.</p></dd><dt><code>(read-u8vector-n! </code><var>input-port</var> <var>octets</var> <var>start</var> <var>count</var><code>)</code></dt><dd><p><var>Count</var> must be an exact, non-negative integer, specifying the number of octets to be read. <var>Octets</var> must be a u8vector with at least <code>(+ <var>start</var> <var>count</var>)</code> elements. This tries to read <var>count</var> octets. If <var>count</var> or more octets are available before the next end of file, they are written into <var>octets</var> starting at index <var>start</var>, and it returns <var>count</var>. If fewer octets are available before the next end of file, it writes the available octets into <var>octets</var> starting at index <var>start</var>, and it returns the number of octets actually read. In either case, the input port is updated to point exactly past the data read. If end of file has been reached, this returns <code>#f</code>, and it updated the input port to points just past the end of file. This procedure will block until either data is available or end of file is reached.</p><p>For a stream input port, this calls <a href="#input-u8vector-n!"><code>input-u8vector-n!</code></a> on the underlying input stream, updates the underlying input stream to the second return value, and returns <code>input-u8vector-n!</code>'s first return value.</p></dd><dt><code>(read-u8vector-all </code><var>input-port</var><code>)</code></dt><dd><p>If data is available before the next end of file, this returns a u8vector containing all octets until that end of file. If not, <code>read-u8vector-all</code> returns <code>#f</code>. The input stream is updated to point just past the end of file. Note that this function may block indefinitely on ports connected to interactive devices, even though data is available.</p><p>For a stream input port, this calls <a href="#input-u8vector-all"><code>input-u8vector-all</code></a> on the underlying input stream, updates the underlying input stream to the second return value, and returns <code>input-u8vector-all</code>'s first return value.</p></dd><dt><code>(read-string </code><var>input-port</var><code>)</code></dt><dd><p>If any data representing a string is available before the next end of file, this returns a string of non-zero size containing the UTF-8 decoding of that data. The input port is updated to point just past the data read. If an end of file has been reached, it returns <code>#f</code>, and the input port is updated to point just past the end of file. This procedure will block until either data is available or end of file is reached.</p><p>For a stream input port, this calls <a href="#input-string"><code>input-string</code></a> on the underlying input stream, updates the underlying input stream to the second return value, and returns <code>input-string</code>'s first return value.</p></dd><dt><code>(read-char </code><var>input-port</var><code>)</code></dt><dd><p>If a char is available before the next end of file, this returns that char, and the input port is updated to point past the data read. If an end of file has been reached, this returns <code>#f</code>, and the input code returned points just past the end of file. This procedure will block until either data is available or end of file is reached.</p><p>For a stream input port, this calls <a href="#input-char"><code>input-char</code></a> on the underlying input stream, updates the underlying input stream to the second return value, and returns <code>input-char</code>'s first return value.</p></dd><dt><code>(read-string-n </code><var>n</var><code>)</code></dt><dt><code>(read-string-n </code><var>n</var> <var>input-port</var><code>)</code></dt><dd><p><var>N</var> must be an exact, non-negative integer, specifying the number of chars to be read.It tries to read <var>n</var> chars. If <var>n</var> or more chars are available before the next end of file, it returns a string of size <var>n</var>consisting of those chars. If fewer chars are available before the next end of file, it returns a string containing those chars. In either case, the input port is updated to point exactly past the data read. If end of file has been reached, it returns <code>#f</code>, and the input port is updated to point just past the end of file. This procedure will block until either data is available or end of file is reached.</p><p>For a stream input port, this calls <a href="#input-string-n"><code>input-string-n</code></a> on the underlying input stream, updates the underlying input stream to the second return value, and returns <code>input-string-n</code>'s first return value.</p></dd><dt><code>(read-string-n! </code><var>input-port</var> <var>string</var> <var>start</var> <var>count</var><code>)</code></dt><dd><p><var>Count</var> must be an exact, non-negative integer, specifying the number of chars to be read.This returns two values: a value and another input stream. The input stream returned points exactly past the data read. It tries to read <var>count</var> chars. If <var>count</var> or more chars are available before the next end of file, they are written into <var>string</var> starting at index <var>start</var>, and it returns <var>count</var> as the value. If fewer chars are available before the next end of file, it writes the available chars into <var>string</var> starting at index <var>start</var>, and it returns the number of chars actually read as the value. If end of file has been reached, it returns <code>#f</code>, and the input stream returned points just past the end of file. This procedure will block until either data is available or end of file is reached.</p><p>For a stream input port, this calls <a href="#input-string-n!"><code>input-string-n!</code></a> on the underlying input stream, updates the underlying input stream to the second return value, and returns <code>input-string-n!</code>'s first return value.</p></dd><dt><code>(read-string-all </code><var>input-port</var><code>)</code></dt><dd><p>If data is available before the next end of file, the value returned is a string contains all text until the next end of file. If no data is available, the value is <code>#f</code>. The input port is updated to point just past the end of file. Note that this function may block indefinitely on streams connected to interactive readers, even though data is available.</p><p>For a stream input port, this calls <a href="#input-string-all"><code>input-string-all</code></a> on the underlying input stream, updates the underlying input stream to the second return value, and returns <code>input-string-all</code>'s first return value.</p></dd><dt><code>(peek-u8 </code><var>input-port</var><code>)</code></dt><dd><p>This is the same as <code>read-u8</code>, but does not advance the port.</p><p>For a stream input port, this calls <a href="#input-u8"><code>input-u8</code></a> on the underlying input stream, but does not update the underlying input stream. It returns <code>input-u8</code>'s first return value.</p></dd><dt><code>(peek-char </code><var>input-port</var><code>)</code></dt><dd><p>This is the same as <code>read-char</code>, but does not advance the port.</p><p>For a stream input port, this calls <a href="#input-char"><code>input-char</code></a> on the underlying input stream, but does not update the underlying input stream. It returns <code>input-char</code>'s first return value.</p></dd><dt><code>(port-eof? </code><var>input-port</var><code>)</code></dt><dd><p>Returns <code>#t</code> if the port is currently pointing at an end-of-file, <code>#f</code> otherwise.</p><p>For a stream input port, this returns the result of calling <a href="#stream-eof?"><code>stream-eof?</code></a> on the input stream underlying <var>input-port</var>.</p></dd><dt><code>(input-port-position </code><var>input-port</var><code>)</code></dt><dd><p>This returns the octet position corresponding to the next octet read from the input stream. This procedure raises an <code>&i/o-operation-not-available-error</code> condition if the port does not support the operation. It is an error to apply this procedure to a closed port, a transcoded port, or a stream input port on a truncated stream, or a translated stream.</p><p>For a stream input port, this calls <a href="#input-stream-position"><code>input-stream-position</code></a> on the underlying input stream and returns the result.</p></dd><dt><code>(close-input-port </code><var>input-port</var><code>)</code></dt><dd><p>This closes <var>input-port</var>, rendering the port incapable of accepting data. This has no effect if the port has already been closed. The return values are unspecified.</p><p>For a stream input port, this calls <a href="#close-input-stream"><code>close-input-stream</code></a> on the stream underlying <var>input-port</var>.</p></dd><dt><code>(open-file-input-port </code><var>filename</var><code>)</code></dt><dt><code>(open-file-input-port </code><var>filename</var> <var>transcoder</var><code>)</code></dt><dd><p>This returns an input port for the named file. The input port may or may not be a stream port. If a transcoder <var>transcoder</var> is specified, the port is appropriately transcoded.</p></dd><dt><code>(open-u8vector-input-port </code><var>octets</var><code>)</code></dt><dt><code>(open-u8vector-input-port </code><var>octets</var> <var>transcoder</var><code>)</code></dt><dd><p>This returns an input port, associated with a u8vector stream on the octet vector <var>octets</var>. The input port may or may not be a stream port. If a transcoder <var>transcoder</var> is specified, the port is appropriately transcoded.</p></dd><dt><code>(open-string-input-port </code><var>string</var><code>)</code></dt><dt><code>(open-string-input-port </code><var>string</var> <var>transcoder</var><code>)</code></dt><dd><p>This returns an input port, associated with a u8vector stream on the UTF-8 encoding of string <var>string</var>. The input port may or may not be a stream port. If a transcoder <var>transcoder</var> is specified, the port is appropriately transcoded.</p></dd><dt><code>(call-with-input-port </code><var>input-port</var> <var>proc</var><code>)</code></dt><dd><p>This calls <var>proc</var> with <var>input-port</var> as an argument. If <var>proc</var> returns, then the port is closed automatically and the values returned by <var>proc</var> are returned. If <var>proc</var> does not return, then the port will not be closed automatically, unless it is possible to provide that the port will never again be used for a read operation.</p></dd><dt><code>(standard-input-port)</code></dt><dd><p>Returns an input port connected to standard input, possibly a fresh one on each call. Note that a program should not keep the returned port live for too long without reading from it, as it may be a stream port connected to a standard-input stream, and standard input read in other parts of the program may accumulate buffer space.</p></dd><h4>Output ports</h4><dl><dt><code>(output-port? </code><var>obj</var><code>)</code></dt><dd><p>This returns <code>#t</code> if the argument is an output port, <code>#f</code> otherwise.</p></dd><dt><code>(write-u8vector </code><var>output-port</var> <var>octets</var><code>)</code></dt><dt><code>(write-u8vector </code><var>output-port</var> <var>octets</var> <var>start</var><code>)</code></dt><dt><code>(write-u8vector </code><var>output-port</var> <var>octets</var> <var>start</var> <var>count</var><code>)</code></dt><dd><p><var>Start</var> and <var>count</var> must be non-negative exact integers that default to 0 and <code>(- (u8vector-length <var>octets</var>) <var>start</var>)</code>, respectively.This writes the <var>count</var> octets in u8vector <var>octets</var> starting at index <var>start</var> to the output port. It is an error if the octet vector actually has size less than <var>start</var> + <var>count</var>. The return values are unspecified.</p><p>For a stream output port, this calls <a href="#output-u8vector"><code>output-u8vector</code></a> on the underlying output stream and <var>octets</var>, <code>start</code> and <code>count</code>.</p></dd></dl><dt><code>(write-u8 </code><var>output-port</var> <var>octet</var><code>)</code></dt><dd><p>This writes the octet <var>octet</var> to the output port. The return values are unspecified.</p><p>For a stream output port, this calls <code>output-u8</code> on the underlying output stream and <var>u8</var>. The return values are unspecified.</p></dd><dt><code>(write-string-n </code><var>output-port</var> <var>string</var><code>)</code></dt><dt><code>(write-string-n </code><var>output-port</var> <var>string</var> <var>start</var><code>)</code></dt><dt><code>(write-string-n </code><var>output-port</var> <var>string</var> <var>start</var> <var>count</var><code>)</code></dt><dd><p><var>Start</var> and <var>count</var> must be non-negative exact integers. <var>Start</var> defaults to 0. <var>Count</var> defaults to <code>(- (string-length <var>octets</var>) <var>start</var>)</code>. This writes the UTF-8 encoding of the substring <code>(substring <var>string</var> (+ <var>start</var> <var>count</var>))</code> to the port. The return values are unspecified.</p><p>For a stream output port, this calls <a href="#output-string"><code>output-string</code></a> on the underlying output stream and <var>string</var>, <code>start</code> and <code>count</code>.</p></dd><dt><code>(write-char </code><var>output-port</var> <var>char</var><code>)</code></dt><dd><p>This writes the UTF-8 encoding of the char <var>char</var> to the port. The return values are unspecified.</p><p>For a stream output port, this calls <a href="#output-char"><code>output-char</code></a> on the underlying output stream and <var>char</var>.</p></dd><dt><code>(newline </code><var>output-port</var><code>)</code></dt><dd><p>This is equivalent to <code>(write-char #\newline <var>output-port</var>)</code>. The return values are unspecified.</p></dd><dt><code>(flush-output-port </code><var>output-port</var><code>)</code></dt><dd><p>This flushes any output from the buffer of <var>output-stream</var> to the underlying data or device. The return values are unspecified.</p><p>For a stream output port, this calls <a href="#flush-output-stream"><code>flush-output-stream</code></a> on the underlying output stream.</p></dd><dt><code>(output-port-buffer-mode </code><var>output-port</var><code>)</code></dt><dd><p>This returns the buffer-mode object of <var>output-port</var>.</p><p>For a stream output port, this calls <a href="#output-stream-buffer-mode"><code>output-stream-buffer-mode</code></a> on the underlying output stream.</p></dd><dt><code>(set-output-port-buffer-mode! </code><var>output-port</var> <var>buffer-mode</var><code>)</code></dt><dd><p>If the current buffer mode of <var>output-port</var> is something other than <code>none</code> and <var>buffer-mode</var> is the <code>none</code> buffer-mode object, this will first flush the output port. Then, it sets the buffer-mode object associated with <var>output-port</var> to <var>buffer-mode</var>. The return values are unspecified.</p><p>For a stream output port, this calls <a href="#set-output-stream-buffer-mode!"><code>set-output-stream-buffer-mode!</code></a> on the underlying output stream.</p></dd><dt><code>(output-port-position </code><var>output-port</var><code>)</code></dt><dd><p>This returns the position corresponding to the next octet read from the output stream. This procedure raises an <code>&i/o-operation-not-available-error</code> condition if the stream does not support the operation. It is an error to apply this procedure to a closed port, a transcoded port, or a stream output port on a terminated stream, or a translated stream.</p><p>For a stream output port, this calls <a href="#output-stream-position"><code>output-stream-position</code></a> on the underlying output stream and returns the result.</p></dd><dt><code>(set-output-port-position! </code><var>output-port</var> <var>pos</var><code>)</code></dt><dd><p><var>Pos</var> must be a non-negative exact integer. This flushes the output port and sets the current octet position to <var>pos</var>. This procedure raises an <code>&i/o-operation-not-available-error</code> condition if the stream does not support the operation. It is an error to apply this procedure to a closed port, a transcoded port, or a stream output port on a terminated stream, or a translated stream.</p><p>For a stream output port, this calls <a href="#set-output-stream-position!"><code>set-output-stream-position!</code></a> on the underlying output stream with <var>pos</var> and returns whatever it returns.</p></dd><dt><code>(close-output-port </code><var>output-port</var><code>)</code></dt><dd><p>This closes <var>output-port</var>, rendering the port incapable of delivering data. This has no effect if the port has already been closed. The return values are unspecified.</p><p>For a stream output port, this calls <a href="#close-output-stream"><code>close-output-stream</code></a> on the stream underlying <var>output-port</var>.</p></dd><dt><code>(open-file-output-port </code><var>filename</var> <var>file-options</var><code>)</code></dt><dt><code>(open-file-output-port </code><var>filename</var> <var>file-options</var> <var>transcoder</var><code>)</code></dt><dd><p>This returns an output port for the named file and the specified options. The output port may or may not be a stream port. If a transcoder <var>transcoder</var> is specified, the port is appropriately transcoded.</p></dd><dt><code>(call-with-output-u8vector </code><var>proc</var><code>)</code></dt><dt><code>(call-with-output-u8vector </code><var>proc</var> <var>transcoder</var><code>)</code></dt><dd><p><var>Proc</var> is a procedure accepting one argument. This creates an unbuffered output port connected to a u8vector writer, and calls <var>proc</var> with that output port as an argument. The output port may or may not be a stream port. The call to <code>call-with-u8vector-output-port</code> returns the u8vector associated with the port when <var>proc</var> returns. If a transcoder <var>transcoder</var> is specified, the port is appropriately transcoded.</p></dd><dt><code>(call-with-output-string </code><var>proc</var><code>)</code></dt><dt><code>(call-with-output-string </code><var>proc</var> <var>transcoder</var><code>)</code></dt><dd><p><var>Proc</var> is a procedure accepting one argument. This creates an unbuffered output connected to a u8vector writer, and calls <var>proc</var> with that port as an argument. The output port may or may not be a stream port. The call to <code>call-with-string-output-stream</code> returns the UTF-8 decoding of the u8vector associated with the port when <var>proc</var> returns. If a transcoder <var>transcoder</var> is specified, the port is appropriately transcoded.</p></dd><dt><code>(call-with-output-port </code><var>output-port</var> <var>proc</var><code>)</code></dt><dd><p>This calls <var>proc</var> with <var>output-port</var> as an argument. If <var>proc</var> returns, then the port is closed automatically and the values returned by <var>proc</var> are returned. If <var>proc</var> does not return, then the port will not be closed automatically, unless it is possible to provide that the port will never again be used for a write operation.</p></dd><dt><code>(standard-output-port)</code></dt><dt><code>(standard-error-port)</code></dt><dd><p>Returns a port connected to the standard output or standard error, respectively.</p></dd></dl><h4>Opening files for reading and writing</h4><dl><dt><code>(open-file-input+output-ports </code><var>filename</var> <var>file-options</var><code>)</code></dt><dt><code>(open-file-input+output-ports </code><var>filename</var> <var>file-options</var> <var>transcoder</var><code>)</code></dt><dd><p>This returns an input port and an output port for the named file and the specified options. The ports may or may not be stream ports. If a transcoder <var>transcoder</var> is specified, the ports are appropriately transcoded.</p></dd></dl><h3>Stream ports</h3><p>These are the procedures that are specific to stream ports.</p><h4>Stream input ports</h4><dl><dt><code>(make-stream-input-port </code><var>input-stream</var><code>)</code></dt><dd><p>This creates a stream input port that points to <var>input-stream</var>.</p></dd><dt><code>(stream-input-port? </code><var>obj</var><code>)</code></dt><dd><p>This returns <code>#t</code> if the argument is a stream input port, <code>#f</code> otherwise.</p></dd><dt><code>(input-port-stream </code><var>stream-input-port</var><code>)</code></dt><dd><p>This returns the input stream underlying <var>stream-input-port</var>.</p></dd><dt><code>(set-input-port-stream! </code><var>stream-input-port</var> <var>input-stream</var><code>)</code></dt><dd><p>This sets the input stream underlying <var>stream-input-port</var> to <var>input-stream</var>.</p></dd></dl><h4>Stream output ports</h4><dl><dt><code>(make-stream-output-port </code><var>output-stream</var><code>)</code></dt><dd><p>This creates a stream output port that points to <var>output-stream</var>.</p></dd><dt><code>(stream-output-port? </code><var>obj</var><code>)</code></dt><dd><p>This returns <code>#t</code> if the argument is a stream output port, <code>#f</code> otherwise.</p></dd><dt><code>(output-port-stream </code><var>stream-output-port</var><code>)</code></dt><dd><p>This returns the output stream underlying <var>stream-output-port</var>.</p></dd><dt><code>(set-output-port-stream! </code><var>stream-output-port</var> <var>output-stream</var><code>)</code></dt><dd><p>This sets the output stream underlying <var>stream-output-port</var> to <var>output-stream</var>.</p></dd></dl><h3>Ports from readers and writers</h3><dl><dt><code>(open-reader-input-port </code><var>reader</var><code>)</code></dt><dt><code>(open-reader-input-port </code><var>reader</var> <var>transcoder</var><code>)</code></dt><dd><p>This returns an input port connected to the reader <var>reader</var>.If a transcoder <var>transcoder</var> is specified, the port is appropriately transcoded.</p></dd><dt><code>(open-writer-output-port </code><var>writer</var> <var>buffer-mode</var><code>)</code></dt><dt><code>(open-writer-output-port </code><var>writer</var> <var>buffer-mode</var> <var>transcoder</var><code>)</code></dt><dd><p>This returns an output port connected to the writer <var>writer</var> with buffering according to <var>buffer-mode</var>.If a transcoder <var>transcoder</var> is specified, the port is appropriately transcoded.</p></dd></dl><h1>Design rationale</h1><h3>Encoding</h3><p>Many I/O system implementations allow associating an encoding with a port, allowing the direct use of several different encodings with ports. The problem with this approach is that the encoding/decoding defines a mapping from binary data to text or vice versa. Because of this asymmetry, such mappings do not compose. The result is usually complications and restrictions in the I/O API, such as the inability to mix text or binary data, or the inability to change encoding mid-stream.</p><p>This SRFI avoids this problem by specifying that textual I/O always uses UTF-8. This means that, if the target or source of an I/O stream is to use a different encoding, a translated stream needs to be used, for which this SRFI offers the required facilities. This means that text decoders or encoders are expressed as binary-to-binary mappings, and as such compose.</p><h3><code>display</code> vs <code>write</code></h3><p>R5RS calls the procedures for writing something to an output port <code>write-<something></code>. In a previous revision of this SRFI, all were called <code>display-<something></code>. R5RS doesn't offer a consistent rule for naming, as the <code>display</code> and <code>write-char</code> procedures behave identically on character arguments, wherease <code>write</code> and <code>write-char</code> do not.</p><p>Historically, it seems that the <a href="http://zurich.ai.mit.edu/pipermail/rrrs-authors/1985-March.txt">original proposal for the I/O subsystem in RnRS</a> indeed called the procedure <code>display-char</code>. I do not know why it was renamed---probably for compatibility with Common Lisp, which also has <code>write-char</code>.</p><p>While the procedures in this SRFI follow a consistent naming scheme, consistency is an issue for what's <code>read</code> and <code>write</code>in R5RS. The naming scheme proposed here suggests they be called <code>read-datum</code> and <code>write-datum</code>.</p><h3><code>char-ready?</code></h3><p>This SRFI intentionally does not provide <code>char-ready?</code>, which is part of R5RS. The original intention of the procedure seems to have been to interface with something like Unix <code>select(2)</code>. With multi-byte encodings such as UTF-8, this is no longer sufficient: the procedure would really have to look at the actual input data in order to determine whether a complete character is actually present. This makes realistic implementations of <code>char-ready?</code> inconsistent with the user's expectations. A procedure <code>byte-ready?</code> would be more consistent. On the other hand, such a procedure is rarely useful in real-world programs, hard to specify to the point where it would be portably usable, and complicates all layers of the I/O system, as readers would have to provide an additional member procedure to enable its implementation. Moreover, a <code>select(2)</code>-like implementation is not possible on all plattforms and all types of ports. Consequently, <code>char-ready?</code> and <code>byte-ready?</code>are not part of this SRFI.</p><h3><code>display</code></h3><p>This SRFI does not provide <code>display</code>, which is part of R5RS. <code>Display</code> is woefully underspecified, and mostly used for debug output. It seems <code>display</code> should be replaced by a procedure for formatted output, possibly augmented by handling of dynamically-bound "current ports".</p><h3>Optional ports and argument order for imperative I/O</h3><p>The argument order of the Imperative I/O layer is different from R5RS: The port is always at the beginning, and it is mandatory.For a rationale, see <a href="http://srfi.schemers.org/srfi-68/mail-archive/msg00031.html">the message by Taylor Campbell</a> on the subject.</p><h3>No distinct end of file object</h3><p>In R5RS, the distinct type of end of file objects is primarily for the benefit of <code>read</code>, where end of file must be denoted by an object that <code>read</code> cannot normally return as a result of parsing the input. However, it does not seem necessary to drag in the complications of this separate object into the other I/O operations, where <code>#f</code> is perfectly adequate to represent end of file.</p><h1>Reference Implementation</h1><p><a href="comprehensive-io-reference.tar.gz">Here</a> is a tarball containing a reference implementation of this SRFI. It only runs on a version of Scheme 48 that has not been released at the time of writing in this SRFI.</p><p>However, its actual dependencies on Scheme 48 idiosyncracies are few. Chief are its use of the module system, which is easily replaced by another, and the implementation of Unicode. To implement primitive readers and writers on files, the code only relies on suitable library procedures to open the files, and <code>read-byte</code> and <code>write-byte</code> procedures to read or write single bytes from a (R5RS) port, as well as a <code>force-output</code> procedure to flush a port.</p><p>The reference implementation has not been highly tuned, but I have spent a modest amount of time making the code deal with buffers in an economic buffer. Because of this, the code is more complicated than it needs to be, but hopefully also more usable as a basis for implementing this SRFI in actual Scheme systems.</p><h1>Examples</h1><p>Many examples are adapted from <a href="http://www.standardml.org/Basis/">The Standard ML Basis Library</a> edited by Emden R. Gansner and John H. Reppy. Cambrige University Press, 2004.</p><p>The code makes liberal use of SRFIs <a href="http://srfi.schemers.org/srfi-1/">1</a> (List Library), <a href="http://srfi.schemers.org/srfi-11/">11</a> (Syntax for receiving multiple values), <a href="http://srfi.schemers.org/srfi-26/">26</a> (Notation for Specializing Parameters without Currying).</p><p>The tarball with the reference implementation contains these examples along with test cases for them.</p><p>This customized reader reads from a list of octet vectors. A null octet vector yields EOF. Procedures for defining streams based on such readers follow.</p><pre>(define (open-u8vectors-reader bs)
(let* ((pos 0))
(make-simple-reader
"<octet vectors>"
bs
5 ; for debugging
(lambda (u8vector start count)
(cond
((null? bs)
0)
(else
(let* ((b (car bs))
(size (u8vector-length b))
(real-count (min count (- size pos))))
(u8vector-copy! b pos
u8vector start
real-count)
(set! pos (+ pos real-count))
(if (= pos size)
(begin
(set! bs (cdr bs))
(set! pos 0)))
real-count))))
;; pretty rough ...
(lambda ()
(if (null? bs)
0
(- (u8vector-length (car bs)) pos)))
#f #f #f ; semantics would be unclear
(lambda ()
(set! bs #f))))) ; for GC
(define (open-strings-reader strings)
(open-u8vectors-reader (map string->utf-8 strings)))
(define (open-u8vectors-input-stream u8vectors)
(open-reader-input-stream (open-u8vectors-reader u8vectors)))
(define (open-strings-input-stream strings)
(open-reader-input-stream (open-u8vectors-reader (map string->utf-8 strings))))
</pre><p>Create a string via a string output port:</p><pre>(define three-lines-string
(call-with-output-string
(lambda (port)
(write-string port "foo") (newline port)
(write-string port "bar") (newline port)
(write-string port "baz") (newline port))))
</pre><p>Note that, for input streams, the successive streams need to be threaded through the program:</p><pre>(define (input-two-lines s)
(let*-values (((line-1 s-2) (input-line s))
((line-2 _) (input-line s-2)))
(values line-1 line-2)))
</pre><p>There may be life after end of file; hence, the following is not guaranteed to return true:</p><pre>(define (at-end?/broken s)
(let ((z (stream-eof? s)))
(let-values (((a s-2) (input-u8vector-some s)))
(let ((x (stream-eof? s-2)))
(equal? z x)))))
</pre><p>... but this is:</p><pre>(define (at-end? s)
(let ((z (stream-eof? s)))
(let-values (((a s-2) (input-u8vector-some s)))
(let ((x (stream-eof? s)))
(equal? z x)))))
</pre><p>Catch an I/O exception:</p><pre>(define (open-it filename)
(guard
(condition
((i/o-error? condition)
(if (message-condition? condition)
(begin
(write-string (standard-error-port)
(condition-message condition))
(newline (standard-error-port))))
#f))
(open-file-input-stream filename)))
</pre><p>Read a file directly:</p><pre>(define (get-contents filename)
(call-with-input-port (open-file-input-port filename)
read-u8vector-all))
</pre><p>Read a file octet-by-octet:</p><pre>(define (get-contents-2 filename)
(call-with-input-port (open-file-input-port filename)
(lambda (port)
(let loop ((accum '()))
(let ((thing (read-u8 port)))
(if (not thing)
(list->u8vector (reverse accum))
(loop (cons thing accum))))))))
(define (list->u8vector l)
(let ((octets (make-u8vector (length l) 0)))
(let loop ((i 0) (l l))
(if (null? l)
octets
(begin
(u8vector-set! octets i (car l))
(loop (+ 1 i) (cdr l)))))))
</pre><p>Read file chunk-by-chunk:</p><pre>(define (get-contents-3 filename)
(call-with-input-port (open-file-input-port filename)
(lambda (port)
(let loop ((accum '()))
(cond
((read-u8vector-some port)
=> (lambda (octets)
(loop (cons octets accum))))
(else
(concatenate-u8vectors (reverse accum))))))))
(define (concatenate-u8vectors list)
(let* ((size (fold + 0 (map u8vector-length list)))
(result (make-u8vector size 0)))
(let loop ((index 0)
(u8vectors list))
(if (null? u8vectors)
result
(let* ((b (car u8vectors))
(size (u8vector-length b)))
(u8vector-copy! b 0 result index size)
(loop (+ index size)
(cdr u8vectors)))))))
</pre><p>Read a file using Stream I/O:</p><pre>(define (get-contents/stream filename)
(call-with-input-stream (open-file-input-stream filename)
(lambda (stream)
(let-values (((octets _) (input-u8vector-all stream)))
octets))))
</pre><p>Read a file octet by octet:</p><pre>(define (get-contents/stream-2 filename)
(call-with-input-stream (open-file-input-stream filename)
(lambda (stream)
(let loop ((accum '()) (stream stream))
(let-values (((octet stream) (input-u8 stream)))
(if (not octet)
(list->u8vector (reverse accum))
(loop (cons octet accum) stream)))))))
</pre><p>Read a file chunk-by-chunk:</p><pre>(define (get-contents/stream-3 filename)
(call-with-input-stream (open-file-input-stream filename)
(lambda (stream)
(let loop ((accum '()) (stream stream))
(let-values (((chunk stream) (input-u8vector-some stream)))
(if chunk
(loop (cons chunk accum) stream)
(concatenate-u8vectors (reverse accum))))))))
</pre><p>Drop a word at the beginning of a stream selectively:</p><pre>(define (eat-thousand stream)
(let-values (((text new-stream)
(input-string-n stream (string-length "thousand"))))
(if (string=? text "thousand")
new-stream
stream)))
</pre><p>Skip whitespace at the beginning of a stream:</p><pre>(define (skip-whitespace stream)
(let-values (((thing new-stream)
(input-char stream)))
(cond
((not thing) stream)
((char-whitespace? thing)
(skip-whitespace new-stream))
(else stream))))
</pre><p>Reading a line could be implemented by scanning forward, then reading a chunk from the original position:</p><pre>(define (my-input-line stream)
(let count ((n 0) (g stream))
(let-values (((thing g*) (input-char g)))
(cond
((not thing)
(if (zero? n)
(values #f g*)
(input-string-n stream n)))
((char=? #\newline thing)
(let*-values (((line _) (input-string-n stream n)))
(values line g*)))
(else
(count (+ 1 n) g*))))))
</pre><p>Write some text to a file:</p><pre>(define (hello myfile)
(call-with-output-stream (open-file-output-stream myfile (file-options truncate create))
(lambda (stream)
(output-string stream "Hello, ")
(output-string stream "world!")
(output-char stream #\newline))))
</pre><p>Extract the reader from a stream, read a octet from it, and then reconstruct a stream from it:</p><pre>(define (after-first filename)
(let ((stream (open-file-input-stream filename)))
(call-with-values
(lambda () (input-stream-reader+constructor stream))
(lambda (reader construct)
(let ((b (make-u8vector 1 0)))
(reader-read! reader b 0 1)
(call-with-input-stream (construct (open-reader-input-stream reader))
(lambda (stream-2)
(let-values (((contents _) (input-string-all stream-2)))
contents))))))))
</pre><p>Extract the reader from a stream, set position, and then reconstruct a stream from it:</p><pre>(define (after-n stream n)
(call-with-values
(lambda () (input-stream-reader+constructor stream))
(lambda (reader construct)
(reader-set-position! reader n)
(call-with-input-stream (construct (open-reader-input-stream reader))
(lambda (stream-2)
(let-values (((contents _) (input-string-all stream-2)))
contents))))))
</pre><p>Translate CR/LF to LF on input:</p><pre>(define (translate-crlf-input original-input-stream wish)
;; state automaton
(define (vanilla input-stream count)
(call-with-values
(lambda ()
(input-u8 input-stream))
(lambda (octet input-stream)
(cond
((not octet) (finish count))
((= 13 octet) (cr input-stream count))
(else (vanilla input-stream (+ 1 count)))))))
(define (cr input-stream count)
(call-with-values
(lambda ()
(input-u8 input-stream))
(lambda (octet input-stream)
(cond
((not octet) (finish (+ 1 count))) ; CR hasn't been counted yet
((= 10 octet)
(call-with-values
(lambda ()
(input-u8vector-n original-input-stream (+ 1 count)))
(lambda (octets _)
(u8vector-set! octets count 10)
(values octets input-stream))))
(else (vanilla input-stream (+ count 1)))))))
(define (finish count)
(if (zero? count)
(let-values (((_ past-eof) (input-u8 original-input-stream)))
(values #f past-eof))
(call-with-values
(lambda ()
(input-u8vector-n original-input-stream count))
(lambda (octets input-stream)
(values octets input-stream)))))
(vanilla original-input-stream 0))
(define (make-crlf-translated-input-stream input-stream)
(make-translated-input-stream input-stream
translate-crlf-input))
</pre><p>Translate LF to CR/LF on output:</p><pre>(define (translate-crlf-output output-stream state data start count)
(cond
((not data))
((u8vector? data)
(let ((end (+ start count)))
(let loop ((index start))
(cond
((u8vector-index data 10 index end)
=> (lambda (lf-index)
(output-u8vector output-stream data index (- lf-index index))
(output-u8 output-stream 13)
(output-u8 output-stream 10)
(loop (+ 1 lf-index))))
(else
(output-u8vector output-stream data index (- end index)))))))
((= data 10)
(output-u8 output-stream 13)
(output-u8 output-stream 10))
(else
(output-u8 output-u8 data)))
(unspecific))
(define (u8vector-index u8vector octet start end)
(let loop ((index start))
(cond
((>= index end)
#f)
((= octet (u8vector-ref u8vector index))
index)
(else
(loop (+ 1 index))))))
</pre><p>Algorithmic reader producing an infinite stream of blanks:</p><pre>(define (make-infinite-blanks-reader)
(make-simple-reader "<blanks, blanks, and more blanks>"
#f
4096
(lambda (octets start count)
(let loop ((index 0))
(if (>= index count)
index
(begin
(u8vector-set! octets (+ start index) 32)
(loop (+ 1 index))))))
(lambda ()
1000) ; some number
#f #f #f
unspecific))
</pre><p>Transcoder round trip:</p><pre>(define (transcoder-round-trip transcoder text)
(let* ((coded
(call-with-u8vector-output-stream
(lambda (output-stream)
(let ((output-stream
(transcode-output-stream output-stream transcoder)))
(output-string output-stream text)))))
(input-stream (open-u8vector-input-stream coded))
(input-stream (transcode-input-stream input-stream transcoder)))
(let-values (((text _) (input-string-all input-stream)))
text)))
</pre><p>Decoding UTF-32LE via transcoders:</p><pre>(define (decode-utf-32le octets)
(let* ((input-stream (open-u8vector-input-stream octets))
(input-stream (transcode-input-stream input-stream
(transcoder (codec utf-32le-codec)))))
(let-values (((text _) (input-string-all input-stream)))
text)))
</pre><h1>Acknowledgements</h1><p>Sebastian Egner provided valuable comments on a draft of this SRFI.</p><h1>References</h1><ul><li><a href="http://www.standardml.org/Basis/">The Standard ML Basis Library</a> edited by Emden R. Gansner and John H. Reppy. Cambrige University Press, 2004.</li><li><a href="http://www.unicode.org/">The Unicode Home Page</a></li></ul></body><H1>Copyright</H1>
Copyright (C) Michael Sperber (2005). All Rights Reserved.
<p>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
<p>
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
<p>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
<hr>
<address>Editor: <a href="mailto:srfi-editors@srfi.schemers.org">Francisco Solsona</a></address>
</body></html>