Input and Output
11.1 Ports
By definition, ports in MzScheme produce and consume bytes. When a
port is provided to a character-based operation, such as
read, the port's bytes are read and interpreted as a UTF-8
encoding of characters (see also section 1.2.3). Thus, reading a
single character may require reading multiple bytes, and a procedure
like char-ready? may need to peek several bytes into the
stream to determine whether a character is available. In the case of
a byte stream that does not correspond to a valid UTF-8 encoding,
functions such as read-char may need to peek one byte ahead
in the stream to discover that the stream is not a valid encoding.
When an input port produces a sequence of bytes that is not a valid UTF-8 encoding in a character-reading context, then bytes that constitute an invalid sequence are converted to the character ``?''. Specifically, bytes 255 and 254 are always converted to ``?'', bytes in the range 192 to 253 produce ``?'' when they are not followed by bytes that form a valid UTF-8 encoding, and bytes in the range 128 to 191 are converted to ``?'' when they are not part of a valid encoding that was started by a preceding byte in the range 192 to 253. To put it another way, when reading a sequence of bytes as characters, a minimal set of bytes are changed to 6330 so that the entire sequence of bytes is a valid UTF-8 encoding.
See section 3.6 for procedures that facilitate conversions
using UTF-8 or other encodings. See also
reencode-input-port and reencode-output-port
in Chapter 35
in PLT MzLib: Libraries Manual for obtaining a UTF-8-based port from one
that uses a different encoding of characters.
(port? v) returns #t if either
( or input-port? v)( is output-port? v)#t,
#f otherwise.
(port-closed? port) returns #t if the input or output
port port is closed, #f otherwise.
(file-stream-port? port) returns #t if the given port is
a file-stream port (see section 11.1.6, #f otherwise.
(terminal-port? port) returns #t if the given port is
attached to an interactive terminal, #f otherwise.
11.1.1 End-of-File Constant
The global variable eof is bound to the end-of-file
value. The standard Scheme predicate eof-object? returns
#t only when applied to this value.
Reading from a port produces an end-of-file result when the port has no more data, but some ports may also return end-of-file mid-stream. For example, a port connected to a Unix terminal returns an end-of-file when the user types control-d; if the user provides more input, the port returns additional bytes after the end-of-file.
11.1.2 Current Ports
The standard Scheme procedures current-input-port and
current-output-port are implemented as parameters in
MzScheme. See section 7.9.1.2 for more information.
11.1.3 Opening File Ports
The open-input-file and open-output-file
procedures accept an optional flag argument after the filename that
specifies a mode for the file:
'binary-- bytes are returned from the port exactly as they are read from the file. Binary mode is the default mode.'text-- return and linefeed bytes (10 and 13) are written to and read from the file are filtered by the port in a platform specific manner:Unix and Mac OS X: no filtering occurs.
Windows reading: a return-linefeed combination from a file is returned by the port as a single linefeed; no filtering occurs for return bytes that are not followed by a linefeed, or for a linefeed that is not preceded by a return.
Windows writing: a linefeed written to the port is translated into a return-linefeed combination in the file; no filtering occurs for returns.
In Windows,
'textmode works only with regular files; attempting to use'textwith other kinds of files triggers anexn:fail:filesystemexception.
The open-output-file procedure can also take a flag
argument that specifies how to proceed when a file with the specified
name already exists:
'truncate/replace-- try'truncate; if it fails, try'replace'append-- append to the end of the file under Unix and Mac OS X; under Windows,'appendis equivalent to'update, except that the file position is immediately set to the end of the file after opening it'update-- open an existing file without truncating it; if the file does not exist, theexn:fail:filesystemexception is raised
The open-input-output-file procedure takes the same
arguments as , but it produces two values:
an input port and an output port. The two ports are connected in that
they share the underlying file device. This procedure is intended for
use with special devices that can be opened by only one process, such
as COM1 in Windows. For regular files, sharing the device can
be confusing. For example, using one port does not automatically
flush the other port's buffer (see section 11.1.6 for more
information about buffers), and reading or writing in one port moves
the file position (if any) for the other port. For regular files, use
separate open-output-fileopen-input-file and open-output-file calls
to avoid confusion.
Extra flag arguments are passed to in any
order. Appropriate flag arguments can also be passed as the last
argument(s) to open-output-filecall-with-input-file,
with-input-from-file, call-with-output-file,
and with-output-to-file. When conflicting flag arguments
(e.g., both 'error and 'replace) are provided to
, open-output-filewith-output-to-file, or
call-with-output-file, the
exn:fail:contract exception is raised.
Both with-input-from-file and
with-output-to-file close the port they create if control
jumps out of the supplied thunk (either through a continuation or an
exception), and the port remains closed if control jumps back into
the thunk. The current input or output port is installed and restored
with parameterize (see section 7.9.2).
See section 11.1.6 for more information on file ports. When an input or output file-stream port is created, it is placed into the management of the current custodian (see section 9.2).
11.1.4 Pipes
(make-pipe [limit-k input-name-v output-name-v]) returns two port
values (see section 2.2): the first port is an input port and the
second is an output port. Data written to the output port is read
from the input port. The ports do not need to be explicitly closed.
The optional limit-k argument can be #f or a positive
exact integer. If limit-k is omitted or #f, the new
pipe holds an unlimited number of unread bytes (i.e., limited only by
the available memory). If limit-k is a positive number, then
the pipe will hold at most limit-k unread/unpeeked bytes;
writing to the pipe's output port thereafter will block until a read
or peek from the input port makes more space available. (Peeks
effectively extend the port's capacity until the peeked bytes are
read.)
The optional input-name-v and output-name-v are used as
the names for the returned input and out ports, respectively, if they
are supplied. Otherwise, the name of each port is 'pipe.
(pipe-content-length pipe-port) returns the number of bytes
contained in a pipe, where pipe-port is either of the pipe's
ports produced by make-pipe. The pipe's content length
counts all bytes that have been written to the pipe and not yet read
(though possibly peeked).
11.1.5 String Ports
Scheme input and output can be read from or collected into a string or byte string:
(open-input-bytesbytes[name-v])creates an input port that reads characters frombytes(see section 3.6). Modifyingbytesafterward does not affect the byte stream produced by the port. The optionalname-vargument is used as the name for the returned port; the default is'string.(open-input-stringstring[name-v])creates an input port that reads bytes from the UTF-8 encoding (see section 1.2.3) ofstring. The optionalname-vargument is used as the name for the returned port; the default is'string.(open-output-bytes[name-v])creates an output port that accumulates the output into a byte string. The optionalname-vargument is used as the name for the returned port; the default is'string.(open-output-string[name-v])creates an output port that accumulates the output into a byte string. This procedure is the same asopen-output-bytes.(get-output-bytesstring-output-port[reset? start-k end-k])returns the bytes accumulated instring-output-portso far in a freshly-allocated byte string (including any bytes written after the port's current position, if any). Ifreset?is true, then all bytes are removed from the port, and the port's position is reset to0; ifreset?is#f(the default), then all bytes remain in the port for further accumulation (so they are returned for later calls toget-output-bytesorget-output-string), and the port's position is unchanged. Thestart-kandend-karguments specify the range of bytes in the port to return; supplyingstart-kandend-kis the same as usingsubbyteson the result ofget-output-bytes, but supplying them toget-output-bytescan avoid an allocation. Theend-kargument can be#f, which corresponds to not passing a second argument tosubbytes.(get-output-stringstring-output-port)returns(; see also section 3.6.bytes->string/utf-8(get-output-bytesstring-output-port) #\?)
String input and output ports do not need to be explicitly closed. The
procedure, described in section 11.1.6,
works for string ports in position-setting mode.file-position
Example:
(define i (open-input-string"hello world")) (define o (open-output-string)) (write(readi) o) (get-output-stringo) ; =>"hello"
11.1.6 File-Stream Ports
A port created by , open-input-file,
open-output-file, and related functions is a file-stream
port. The initial input, output, and error ports in stand-alone
MzScheme are also file-stream ports. The subprocessfile-stream-port?
predicate recognizes file-stream ports.
An input port is block buffered by default, which means that on any
read, the buffer is filled with immediately-available bytes to speed
up future reads. Thus, if a file is modified between a pair of reads
to the file, the second read can produce stale data. Calling
to set an input port's file position flushes
its buffer.file-position
Most output ports are block buffered by default, but a terminal output port is line buffered, and the error output port is unbuffered. An output buffer is filled with a sequence of written bytes to be committed as a group, either when the buffer is full (in block mode) or when a newline is written (in line mode).
A port's buffering can be changed via
(described below). The two ports produced by
file-stream-buffer-modeopen-input-output-file have independent buffers.
The following procedures work primarily on file-stream ports:
(flush-output[output-port])forces all buffered data in the given output port to be physically written. Ifoutput-portis omitted, then the current output port is flushed. Only file-stream ports and custom ports (see section 11.1.7) use buffers; when called on a port without a buffer,has no effect.flush-outputBy default, a file-stream port is block-buffered, but this behavior can be modified with
file-stream-buffer-mode. In addition, the initial current output and error ports are automatically flushed when31,read,read-line,read-bytes, etc. are performed on the initial standard input port.read-string(file-stream-buffer-modeport[mode-symbol])gets or sets the buffer mode forport, if possible. All file-stream ports support setting the buffer mode, TCP ports (see section 11.4) support setting and getting the buffer mode, and custom ports (see section 11.1.7) may support getting and setting buffer modes.If
mode-symbolis provided, it must be one of'none,'line(output only), or'block, and the port's buffering is set accordingly. If the port does not support setting the mode, theexn:failexception is raised.If
mode-symbolis not provided, the current mode is returned, or#fis returned if the mode cannot be determined. Iffile-stream-portis an input port andmode-symbolis'line, theexn:fail:contractexception is raised.For an input port, peeking always places peeked bytes into the port's buffer, even when the port's buffer mode is
'none; furthermore, on some platforms, testing the port for input (viaorchar-ready?sync) may be implemented with a peek. If an input port's buffer mode is'none, then at most one byte is read for,read-bytes-avail!*,read-bytes-avail!, orpeek-bytes-avail!*; if any bytes are buffered in the port (e.g., to satisfy a previous peek), the procedures may access multiple buffered bytes, but no further bytes are read.peek-bytes-avail!(file-positionport)returns the current read/write position ofport. For file-stream and string ports,(file-positionport k-or-eof)sets the read/write position tok-or-eofrelative to the beginning of the file/string ifk-or-eofis a number, or to the current end of the file/string ifk-or-eofiseof. In position-setting mode,file-positionraises theexn:fail:contractexception for port kinds other than file-stream and string ports. Callingwithout a position on a non-file/non-string input port returns the number of bytes that have been read from that port if the position is known (see section 11.2.1.1), otherwise thefile-positionexn:fail:filesystemexception is raised.When
(file-positionsets the positionportk)kbeyond the current size of an output file or string, the file/string is enlarged to sizekand the new region is filled with#\nul. Ifkis beyond the end of an input file or string, then reading thereafter returnswithout changing the port's position.eofNot all file-stream ports support setting the position. If
is called with a position argument on such a file-stream port, thefile-positionexn:fail:filesystemexception is raised.When changing the file position for an output port, the port is first flushed if its buffer is not empty. Similarly, setting the position for an input port clears the port's buffer (even if the new position is the same as the old position). However, although input and output ports produced by
open-input-output-fileshare the file position, setting the position via one port does not flush the other port's buffer.(port-file-identityfile-stream-port)returns an exact positive integer that represents the identity of the device and file read or written byfile-stream-port. For two ports whose open times overlap, the result ofport-file-identityis the same for both ports if and only if the ports access the same device and file. For ports whose open times do not overlap, no guarantee is provided for the port identities (even if the ports actually access the same file) -- except as can be inferred through relationships with other ports. Iffile-stream-portis closed, theexn:failexception is raised. Under Windows 95, 98, and Me, iffile-stream-portis connected to a pipe instead of a file, theexn:fail:filesystemexception is raised.
11.1.7 Custom Ports
The and make-input-port procedures
create custom ports with arbitrary control procedures. Correctly
implementing a custom port can be tricky, because it amounts to
implementing a device driver. Custom ports are mainly useful to
obtain fine control over the action of committing bytes as read or
written.make-output-port
Many simple port variations can be implemented using threads and
pipes. For example, if get-next-char is a function that
produces either a character or , it can be turned into an
input port as follows
eof
(let-values ([(r w) (make-pipe4096)]) ;; Create a thread to move chars fromget-next-charto the pipe (thread(lambda () (let loop () (let ([v (get-next-char)]) (if (eof-object? v) (close-output-portw) (begin (write-charv w) (loop))))))) ;; Return the read end of the pipe r)
The port.ss in MzLib provides several other port constructors; see Chapter 35 in PLT MzLib: Libraries Manual.
11.1.7.1 Custom Input
(make-input-port name-v read-proc optional-peek-proc close-proc [optional-progress-evt-proc optional-commit-proc optional-location-proc count-lines!-proc init-position optional-buffer-mode-proc])
creates an input port. The port is immediately open for reading. If
close-proc procedure has no side effects, then the port need
not be explicitly closed.
name-v-- the name for the input port, which is reported byobject-name(see section 6.2.3).read-proc-- a procedure that takes a single argument: a mutable byte string to receive read bytes. The procedure's result is one of the following:the number of bytes read, as an exact, non-negative integer;
;eofa procedure of arity four (representing a ``special'' result, as discussed further below) and optionally of arity zero, but a procedure result is allowed only when
optional-peek-procis not#f; ora synchronizable event (see section 7.7) that becomes ready when the read is complete (roughly): the event's value can one of the above three results or another event like itself; in the last case, a reading process loops with
until it gets a non-event result.sync
The
read-procprocedure must not block indefinitely. If no bytes are immediately available for reading, theread-procmust return0or an event, and preferably an event (to avoid busy waits). Theread-procshould not return0(or an event whose value is0) when data is available in the port, otherwise polling the port will behave incorrectly. An event result from an event can also break polling.If the result of a
read-proccall is not one of the above values, theexn:fail:contractexception is raised. If a returned integer is larger than the supplied byte string's length, theexn:fail:contractexception is raised. Ifoptional-peek-procis#fand a procedure for a special result is returned, theexn:fail:contractexception is raised.The
read-procprocedure can report an error by raising an exception, but only if no bytes are read. Similarly, no bytes should be read if, an event, or a procedure is returned. In other words, no bytes should be lost due to spurious exceptions or non-byte data.eofA port's reading procedure may be called in multiple threads simultaneously (if the port is accessible in multiple threads), and the port is responsible for its own internal synchronization. Note that improper implementation of such synchronization mechanisms might cause a non-blocking read procedure to block indefinitely.
If
optional-peek-proc,optional-progress-evt-proc, andoptional-commit-procare all provided and non-#f, then the following is an acceptable implementation ofread-proc:(lambda (bstr) (let* ([progress-evt (progress-evt-proc)] [v (peek-proc bstr 0 progress-evt)]) (cond [(
sync/timeout0 progress-evt) 0] ; try again [(evt?v) (wrap-evt v (lambda (x) 0))] ; sync, then try again [(and (number?v) (zero?v)) 0] ; try again [else (if (optional-commit-proc (if (number?v) v 1) progress-evt always-evt) v ; got a result 0)]))) ; try againAn implementor may choose not to implement the
optional-procedures, however, and even an implementor who does supplyoptional-procedures may provide a differentread-procthat uses a fast path for non-blocking reads.optional-peek-proc-- either#for a procedure that takes three arguments:a mutable byte string to receive peeked bytes;
a non-negative number of bytes (or specials) to skip before peeking; and
either
#for a progress event produced byoptional-progress-evt-proc.
The results and conventions for
optional-peek-procare mostly the same as forread-proc. The main difference is in the handling of the progress event, if it is not#f. If the given progress event becomes ready, theoptional-peek-procmust abort any skip attempts and not peek any values. In particular,optional-peek-procmust not peek any values if the progress event is initially ready.Unlike
read-proc,optional-peek-procshould produce#f(or an event whose value is#f) if no bytes were peeked because the progress event became ready. Likeread-proc, a0result indicates that another attempt is likely to succeed, so0is inappropriate when the progress event is ready. Also likeread-proc,optional-peek-procmust not block indefinitely.The skip count provided to
optional-peek-procis a number of bytes (or specials) that must remain present in the port -- in addition to the peek results -- when the peek results are reported. If a progress event is supplied, then the peek is effectively canceled when another process reads data before the given number can be skipped. If a progress event is not supplied and data is read, then the peek must effectively restart with the original skip count.The system does not check that multiple peeks return consistent results, or that peeking and reading produce consistent results.
If
optional-peek-procis#f, then peeking for the port is implemented automatically in terms of reads, but with several limitations. First, the automatic implementation is not thread-safe. Second, the automatic implementation cannot handle special results (non-byte and non-eof), soread-proccannot return a procedure for a special whenoptional-peek-procis#f. Finally, the automatic peek implementation is incompatible with progress events, so ifoptional-peek-procis#f, thenprogress-evt-procandoptional-commit-procmust be#f. See alsomake-input-port/peek-to-readin Chapter 35 in PLT MzLib: Libraries Manual.close-proc-- a procedure of zero arguments that is called to close the port. The port is not considered closed until the closing procedure returns. The port's procedures will never be used again via the port after it is closed. However, the closing procedure can be called simultaneously in multiple threads (if the port is accessible in multiple threads), and it may be called during a call to the other procedures in another thread; in the latter case, any outstanding reads and peeks should be terminated with an error.optional-progress-evt-proc-- either#f(the default), or a procedure that takes no arguments and returns an event. The event must become ready only after data is next read from the port or the port is closed. After the event becomes ready, it must remain so. (See alsosemaphore-peek-evtin section 7.4.)If
optional-progress-evt-procis#f, thenport-provides-progress-evts?applied to the port will produce#f, and the port will not be a valid argument toport-progress-evt.optional-commit-proc-- either#f(the default), or a procedure that takes three arguments:an exact, positive integer kr;
a progress event produced by
optional-progress-evt-proc;an event,
done-evt, that is either a channel-put event, channel, semaphore, semaphore-peek event, always event, or never event.
A commit corresponds to removing data from the stream that was previously peeked, but only if no other process removed data first. (The removed data does not need to be reported, because it has been peeked already.) More precisely, assuming that kp bytes, specials, and mid-stream
s have been previously peeked or skipped at the start of the port's stream,eofoptional-commit-procmust satisfy the following constraints:It must return only when the commit is complete or when the given progress event becomes ready.
It must commit only if kp is positive.
If it commits, then it must do so with either kr items or kp items, whichever is smaller, and only if kp is positive.
It must never choose
done-evtin a synchronization after the given progress event is ready, or afterdone-evthas been synchronized once.It must not treat any data as read from the port unless
done-evtis chosen in a synchronization.It must not block indefinitely if
done-evtis ready; it must return soon after the read completes or soon after the given progress event is ready, whichever is first.It can report an error by raising an exception, but only if no data is committed. In other words, no data should be lost due to an exception, including a break exception.
It must return a true value if data is committed,
#fotherwise. When it returns a value, the given progress event must be ready (perhaps because data was just committed).It must raise an exception if no data (including
) has been peeked from the beginning of the port's stream, or if it would have to block indefinitely to wait for the given progress event to become ready.eof
A call to
optional-commit-procisparameterize-breaked to disable breaks.optional-location-proc-- either#f(the default), or a procedure that takes no arguments and returns three values: the line number for the next item in the port's stream (a positive number or#f), the column number for the next item in the port's stream (a non-negative number or#f), and the position for the next item in the port's stream (a positive number or#f). See also section 11.2.1.1.This procedure is only called if line counting is enabled for the port via
port-count-lines!(in which casecount-lines!-procis called). The,read,read-syntaxread-honu, andread-honu-syntaxprocedures assume that reading a non-whitespace character increments the column and position by one.count-lines!-proc-- a procedure of no arguments that is called if and when line counting is enabled for the port. The default procedure isvoid.init-position-- an exact, positive integer that determines the position of the port's first item, used when line counting is not enabled for the port. The default is1.optional-buffer-mode-proc-- either#f(the default) or a procedure that accepts zero or one arguments. Ifoptional-buffer-mode-procis#f, then the resulting port does not support a buffer-mode setting. Otherwise, the procedure is called with one symbol argument ('blockor'none) to set the buffer mode, and it is called with zero arguments to get the current buffer mode. In the latter case, the result must be'block,'none, or#f(unknown). See section 11.1.6 for more information on buffer modes.
When read-proc or optional-peek-proc (or an event
produced by one of these) returns a procedure, and the procedure is
used to obtain a non-byte result.32 The procedure is called by
,33
read, read-syntaxread-honu, read-honu-syntax,
read-byte-or-special, read-char-or-special,
peek-byte-or-special, or peek-char-or-special. The
special-value procedure can return an arbitrary value, and it will be
called zero or one times (not necessarily before further reads or
peeks from the port). See section 11.2.9 for more details on
the procedure's arguments and result.
If read-proc or optional-peek-proc returns a special
procedure when called by any reading procedure other than
, read, read-syntaxread-honu,
read-honu-syntax, ,
read-char-or-special, peek-char-or-special, or
read-byte-or-special, then the peek-byte-or-specialexn:fail:contract exception is raised.
Examples:
;; A port with no input... ;; Easy:(;; Hard: (define /dev/null-in (open-input-bytes#"")make-input-port'null(lambda (s)eof) (lambda (skip s progress-evt)eof)void(lambda () never-evt) (lambda (k progress-evt done-evt) (error"no successful peeks!")))) (read-char/dev/null-in) ; =>(eofpeek-char/dev/null-in) ; =>(eofread-byte-or-special/dev/null-in) ; =>(eofpeek-byte-or-special/dev/null-in 100) ; =>;; A port that produces a stream of 1s: (define infinite-ones (eofmake-input-port'ones (lambda (s) (bytes-set!s 0 (char->integer#\1)) 1) #fvoid)) (read-string5 infinite-ones) ; =>"11111";; But we can't peek ahead arbitrarily far, because the ;; automatic peek must record the skipped bytes: (peek-string5 (expt2 5000) infinite-ones) ; => error: out of memory ;; An infinite stream of 1s with a specific peek procedure: (define infinite-ones (let ([one! (lambda (s) (bytes-set!s 0 (char->integer#\1)) 1)]) (make-input-port'ones one! (lambda (s skip progress-evt) (one! s))void))) (read-string5 infinite-ones) ; =>"11111";; Now we can peek ahead arbitrarily far: (peek-string5 (expt2 5000) infinite-ones) ; =>"11111";; The port doesn't supply procedures to implement progress events: (port-provides-progress-evts?infinite-ones) ; =>#f(port-progress-evtinfinite-ones) ; error: no progress events ;; Non-byte port results: (define infinite-voids (make-input-port'voids (lambda (s) (lambda args 'void)) (lambda (skip s) (lambda args 'void))void)) (read-charinfinite-voids) ; => error: non-char in an unsupported context (read-char-or-specialinfinite-voids) ; =>';; This port produces 0, 1, 2, 0, 1, 2, etc., but it is not ;; thread-safe, because multiple threads might read and changevoidn. (define mod3-cycle/one-thread (let* ([n 2] [mod! (lambda (s delta) (bytes-set!s 0 (+ 48 (modulo(+ n delta) 3))) 1)]) (make-input-port'mod3-cycle/not-thread-safe (lambda (s) (set! n (modulo(add1n) 3)) (mod! s 0)) (lambda (s skip) (mod! s skip))void))) (read-string5 mod3-cycle/one-thread) ; =>"01201"(peek-string5 (expt2 5000) mod3-cycle/one-thread) ; =>"20120";; Same thing, but thread-safe and kill-safe, and with progress ;; events. Only the server thread touches the stateful part ;; directly. (See the output port examples for a simpler thread-safe ;; example, but this one is more general.) (define (make-mod3-cycle) (define read-req-ch (make-channel)) (define peek-req-ch (make-channel)) (define progress-req-ch (make-channel)) (define commit-req-ch (make-channel)) (define close-req-ch (make-channel)) (define closed? #f) (define n 0) (define progress-sema #f) (define (mod! s delta) (bytes-set!s 0 (+ 48 (modulo(+ n delta) 3))) 1) ;; ---------------------------------------- ;; The server has a list of outstanding commit requests, ;; and it also must service each port operation (read, ;; progress-evt, etc.) (define (serve commit-reqs response-evts) (applysync(handle-evt read-req-ch (handle-read commit-reqs response-evts)) (handle-evt progress-req-ch (handle-progress commit-reqs response-evts)) (handle-evt commit-req-ch (add-commit commit-reqs response-evts)) (handle-evt close-req-ch (handle-close commit-reqs response-evts)) (append(map(make-handle-response commit-reqs response-evts) response-evts) (map(make-handle-commit commit-reqs response-evts) commit-reqs)))) ;; Read/peek request: fill in the string and commit (define ((handle-read commit-reqs response-evts) r) (let ([s (carr)] [skip (cadrr)] [ch (caddrr)] [nack (cadddrr)] [peek? (cddddrr)]) (unless closed? (mod! s skip) (unless peek? (commit! 1))) ;; Add an event to respond: (serve commit-reqs (cons(choice-evtnack (channel-put-evtch (if closed? 0 1))) response-evts)))) ;; Progress request: send a peek evt for the current ;; progress-sema (define ((handle-progress commit-reqs response-evts) r) (let ([ch (carr)] [nack (cdrr)]) (unless progress-sema (set! progress-sema (make-semaphore(if closed? 1 0)))) ;; Add an event to respond: (serve commit-reqs (cons(choice-evtnack (channel-put-evtch (semaphore-peek-evt progress-sema))) response-evts)))) ;; Commit request: add the request to the list (define ((add-commit commit-reqs response-evts) r) (serve (consr commit-reqs) response-evts)) ;; Commit handling: watch out for progress, in which case ;; the response is a commit failure; otherwise, try ;; to sync for a commit. In either event, remove the ;; request from the list (define ((make-handle-commit commit-reqs response-evts) r) (let ([k (carr)] [progress-evt (cadrr)] [done-evt (caddrr)] [ch (cadddrr)] [nack (cddddrr)]) ;; Note: we don't check that k is < the sum of ;; previous peeks, because the entire stream is actually ;; known, but we could send an exception in that case. (choice-evt(handle-evt progress-evt (lambda (x) (syncnack (channel-put-evtch #f)) (serve (remqr commit-reqs) response-evts))) ;; Only create an event to satisfy done-evt if progress-evt ;; isn't already ready. ;; Afterward, if progress-evt becomes ready, then this ;; event-making function will be called again, because ;; the server controls all posts to progress-evt. (if (sync/timeout0 progress-evt) never-evt (handle-evt done-evt (lambda (v) (commit! k) (syncnack (channel-put-evtch #t)) (serve (remqr commit-reqs) response-evts))))))) ;; Response handling: as soon as the respondee listens, ;; remove the response (define ((make-handle-response commit-reqs response-evts) evt) (handle-evt evt (lambda (x) (serve commit-reqs (remqevt response-evts))))) ;; Close handling: post the progress sema, if any, and set ;; theclosed?flag (define ((handle-close commit-reqs response-evts) r) (let ([ch (carr)] [nack (cdrr)]) (set! closed? #t) (when progress-sema (semaphore-postprogress-sema)) (serve commit-reqs (cons(choice-evtnack (channel-put-evtch (void))) response-evts)))) ;; Helper for reads and post-peek commits: (define (commit! k) (when progress-sema (semaphore-postprogress-sema) (set! progress-sema #f)) (set! n (+ n k))) ;; Start the server thread: (define server-thread (thread(lambda () (servenullnull)))) ;; ---------------------------------------- ;; Client-side helpers: (define (req-evt f) (nack-guard-evt(lambda (nack) ;; Be sure that the server thread is running: (thread-resumeserver-thread (current-thread)) ;; Create a channel to hold the reply: (let ([ch (make-channel)]) (f ch nack) ch)))) (define (read-or-peek-evt s skip peek?) (req-evt (lambda (ch nack) (channel-putread-req-ch (list*s skip ch nack peek?))))) ;; Make the port: (make-input-port'mod3-cycle ;; Each handler for the port just sends ;; a request to the server (lambda (s) (read-or-peek-evt s 0 #f)) (lambda (s skip) (read-or-peek-evt s skip #t)) (lambda () ; close (sync(req-evt (lambda (ch nack) (channel-putprogress-req-ch (list*ch nack)))))) (lambda () ; progress-evt (sync(req-evt (lambda (ch nack) (channel-putprogress-req-ch (list*ch nack)))))) (lambda (k progress-evt done-evt) ; commit (sync(req-evt (lambda (ch nack) (channel-putcommit-req-ch (list*k progress-evt done-evt ch nack)))))))) (let ([mod3-cycle (make-mod3-cycle)]) (let ([result1 #f] [result2 #f]) (let ([t1 (thread(lambda () (set! result1 (read-string5 mod3-cycle))))] [t2 (thread(lambda () (set! result2 (read-string5 mod3-cycle))))]) (thread-waitt1) (thread-waitt2) (string-appendresult1 "," result2))) ; =>"02120,10201", maybe (let ([s (make-bytes1)] [progress-evt (port-progress-evtmod3-cycle)]) (peek-bytes-avail!s 0 progress-evt mod3-cycle) ; =>1s ; =>#"1"(port-commit-peeked1 progress-evt (make-semaphore1) mod3-cycle) ; =>#t(sync/timeout0 progress-evt) ; =>progress-evt(peek-bytes-avail!s 0 progress-evt mod3-cycle) ; =>0(port-commit-peeked1 progress-evt (make-semaphore1) mod3-cycle)) ; =>#f(close-input-portmod3-cycle))
11.1.7.2 Custom Output
(make-output-port name-v evt write-proc close-proc [optional-write-special-proc optional-write-evt-proc optional-special-evt-proc optional-location-proc count-lines!-proc init-position optional-buffer-mode-proc])
creates an output port. The port is immediately open for writing. If
close-proc procedure has no side effects, then the port need
not be explicitly closed. The port can buffer data within its
write-proc and optional-write-special-proc procedures.
name-v-- the name for the output port, which is reported byobject-name(see section 6.2.3).evt-- a synchronization event (see section 7.7; e.g., a semaphore or another port). The event is used in place of the output port when the port is supplied to synchronization procedures likesync. Thus, the event should be unblocked when the port is ready for writing at least one byte without blocking, or ready to make progress in flushing an internal buffer without blocking. The event must not unblock unless the port is ready for writing; otherwise, the guarantees ofwill be broken for the output port. Usesyncalways-evtif writes to the port always succeed without blocking.write-proc-- a procedure of five arguments:an immutable byte string containing bytes to write;
a non-negative exact integer for a starting offset (inclusive) into the byte string;
a non-negative exact integer for an ending offset (exclusive) into the byte string;
a boolean;
#findicates that the port is allowed to keep the written bytes in a buffer, and that it is allowed to block indefinitely;#tindicates that the write should not block, and that the port should attempt to flush its buffer and completely write new bytes instead of buffering them;a boolean;
#tindicates that if the port blocks for a write, then it should enable breaks while blocking (e.g., usingsync/enable-break; this argument is always#fif the fourth argument is#t.
The procedure returns one of the following:
a non-negative exact integer representing the number of bytes written or buffered;
#fif no bytes could be written, perhaps because the internal buffer could not be completely flushed;a synchronizable event (see section 7.7) that acts like the result of
write-bytes-avail-evtto complete the write.
Since
write-proccan produce an event, an acceptable implementation ofwrite-procis to pass its first three arguments to the port'soptional-write-evt-proc. Some port implementors, however, may choose not to provideoptional-write-evt-proc(perhaps because writes cannot be made atomic), or may implementwrite-procto enable a fast path for non-blocking writes or to enable buffering.From a user's perspective, the difference between buffered and completely written data is (1) buffered data can be lost in the future due to a failed write, and (2)
flush-outputforces all buffered data to be completely written. Under no circumstances is buffering required.If the start and end indices are the same, then the fourth argument to
write-procwill be#f, and the write request is actually a flush request for the port's buffer (if any), and the result should be0for a successful flush (or if there is no buffer).The result should never be
0if the start and end indices are different, otherwise theexn:fail:contractexception is raised. If a returned integer is larger than the supplied byte-string range, theexn:fail:contractexception is raised.The
#fresult should be avoided, unless the next write attempt is likely to work. Otherwise, if data cannot be written, return an event instead.An event returned by
write-proccan return#for another event like itself, in contrast to events produced bywrite-bytes-avail-evtoroptional-write-evt-proc. A writing process loops withuntil it obtains a non-event result.syncThe
write-procprocedure is always called with breaks disabled, independent of whether breaks were enabled when the write was requested by a client of the port. If breaks were enabled for a blocking operation, then the fifth argument towrite-procwill be#t, which indicates thatwrite-procshould re-enable breaks while blocking.If the writing procedure raises an exception, due either to write or commit operations, it must not have committed any bytes (though it may have committed previously buffered bytes).
A port's writing procedure may be called in multiple threads simultaneously (if the port is accessible in multiple threads). The port is responsible for its own internal synchronization. Note that improper implementation of such synchronization mechanisms might cause a non-blocking write procedure to block.
close-proc-- a procedure of zero arguments that is called to close the port. The port is not considered closed until the closing procedure returns. The port's procedures will never be used again via the port after it is closed. However, the closing procedure can be called simultaneously in multiple threads (if the port is accessible in multiple threads), and it may be called during a call to the other procedures in another thread; in the latter case, any outstanding writes or flushes should be terminated immediately with an error.optional-write-special-proc-- either#f(the default), or a procedure to handlewrite-specialcalls for the port. If#f, then the port does not support special output, andport-writes-special?will return#fwhen applied to the port.If a procedure is supplied, it takes three arguments: the special value to write, a boolean that is
#fif the procedure can buffer the special value and block indefinitely, and a boolean that is#tif the procedure should enable breaks while blocking. The result is one of the following:a non-event true value, which indicates that the special is written;
#fif the special could not be written, perhaps because an internal buffer could not be completely flushed;a synchronizable event (see section 7.7) that acts like the result of
write-special-evtto complete the write.
Since
optional-write-special-proccan return an event, passing the first argument to an implementation ofoption-write-special-evt-procis acceptable as anoptional-write-special-proc.As for
write-proc, the#fresult is discouraged, since it can lead to busy waiting. Also as forwrite-proc, an event produced byoptional-write-special-procis allowed to produce#for another event like itself. Theoptional-write-special-procprocedure is always called with breaks disabled, independent of whether breaks were enabled when the write was requested by a client of the port.optional-write-evt-proc-- either#f(the default) or a procedure of three arguments:an immutable byte string containing bytes to write;
a non-negative exact integer for a starting offset (inclusive) into the byte string, and
a non-negative exact integer for an ending offset (exclusive) into the byte string.
The result is a synchronizable event (see section 7.7) to act as the result of
write-bytes-avail-evtfor the port (i.e., to complete a write or flush), which becomes available only as data is committed to the port's underlying device, and whose result is the number of bytes written.If
optional-write-evt-procis#f, thenport-writes-atomic?will produce#fwith applied to the port, and the port will not be a valid argument to procedures such aswrite-bytes-avail-evt.Otherwise, an event returned by
optional-write-evt-procmust not cause data to be written to the port unless the event is chosen in a synchronization, and it must write to the port if the event is chosen (i.e., the write must appear atomic with respect to the synchronization).If the event's result integer is larger than the supplied byte-string range, the
exn:fail:contractexception is raised by a wrapper on the event. If the start and end indices are the same (i.e., no bytes are to be written), then the event should produce0when the buffer is completely flushed. (If the port has no buffer, then it is effectively always flushed.)If the event raises an exception, due either to write or commit operations, it must not have committed any new bytes (though it may have committed previously buffered bytes).
Naturally, a port's events may be used in multiple threads simultaneously (if the port is accessible in multiple threads). The port is responsible for its own internal synchronization.
optional-write-special-evt-proc-- either#f(the default), or a procedure to handlewrite-special-evtcalls for the port. This argument must be#fif eitheroptional-write-special-procoroptional-write-evt-procis#f, and it must be a procedure if both of those arguments are procedures.If it is a procedure, it takes one argument: the special value to write. The resulting event (with its constraints) is analogous to the result of
optional-write-evt-proc.If the event raises an exception, due either to write or commit operations, it must not have committed the special value (though it may have committed previously buffered bytes and values).
optional-location-proc-- either#f(the default), or a procedure that takes no arguments and returns three values: the line number for the next item written to the port's stream (a positive number or#f), the column number for the next item written to port's stream (a non-negative number or#f), and the position for the next item written to port's stream (a positive number or#f). See also section 11.2.1.1.This procedure is only called if line counting is enabled for the port via
port-count-lines!(in which casecount-lines!-procis called).count-lines!-proc-- a procedure of no arguments that is called if and when line counting is enabled for the port. The default procedure isvoid.init-position-- an exact, positive integer that determines the position of the port's first output item, used when line counting is not enabled for the port. The default is1.optional-buffer-mode-proc-- either#f(the default) or a procedure that accepts zero or one arguments. Ifoptional-buffer-mode-procis#f, then the resulting port does not support a buffer-mode setting. Otherwise, the procedure is called with one symbol argument ('block,'line, or'none) to set the buffer mode, and it is called with zero arguments to get the current buffer mode. In the latter case, the result must be'block,'line,'none, or#f(unknown). See section 11.1.6 for more information on buffer modes.
Examples:
;; A port that writes anything to nowhere: (define /dev/null-out (make-output-port'nullalways-evt (lambda (s start end non-block? breakable?) (- end start))void(lambda (special non-block? breakable?) #t) (lambda (s start end) (wrap-evt always-evt (lambda (x) (- end start)))) (lambda (special) always-evt))) (display"hello" /dev/null-out) ; => void (write-bytes-avail #"hello" /dev/null-out) ; =>5(write-special 'hello /dev/null-out) ; =>#t(sync(write-bytes-avail-evt #"hello" /dev/null-out)) ; =>5;; A part that accumulates bytes as characters in a list, ;; but not in a thread-safe way: (define accum-listnull) (define accumulator/not-thread-safe (make-output-port'accum/not-thread-safe always-evt (lambda (s start end non-block? breakable?) (set! accum-list (appendaccum-list (mapinteger->char(bytes->list (subbytes s start end))))) (- end start))void)) (display"hello" accumulator/not-thread-safe) accum-list ; =>'(#\h #\e #\l #\l #\o);; Same as before, but with simple thread-safety: (define accum-listnull) (define accumulator (let* ([lock (make-semaphore1)] [lock-peek-evt (semaphore-peek-evt lock)]) (make-output-port'accum lock-peek-evt (lambda (s start end non-block? breakable?) (if (semaphore-try-wait?lock) (begin (set! accum-list (appendaccum-list (mapinteger->char(bytes->list (subbytes s start end))))) (semaphore-postlock) (- end start)) ;; Cheap strategy: block until the list is unlocked, ;; then return 0, so we get called again (wrap-evt lock-peek (lambda (x) 0))))void))) (display"hello" accumulator) accum-list ; =>'(#\h #\e #\l #\l #\o);; A port that transforms data before sending it on ;; to another port. Atomic writes exploit the ;; underlying port's ability for atomic writes. (define (make-latin-1-capitalize port) (define (byte-upcase s start end) (list->bytes (map(lambda (b) (char->integer(char-upcase(integer->charb)))) (bytes->list (subbytes s start end))))) (make-output-port'byte-upcase ;; This port is ready when the original is ready: port ;; Writing procedure: (lambda (s start end non-block? breakable?) (let ([s (byte-upcase s start end)]) (if non-block? (write-bytes-avail* s port) (begin (displays port) (bytes-lengths))))) ;; Close procedure --- close original port: (lambda () (close-output-portport)) #f ;; Write event: (and (port-writes-atomic? port) (lambda (s start end) (write-bytes-avail-evt (byte-upcase s start end) port))))) (define orig-port (open-output-string)) (define cap-port (make-latin-1-capitalize orig-port)) (display"Hello" cap-port) (get-output-stringorig-port) ; =>"HELLO"(sync(write-bytes-avail-evt #"Bye" cap-port)) ; =>3(get-output-stringorig-port) ; =>"HELLOBYE"
11.2 Reading and Writing
MzScheme's support for reading and writing includes many extensions compared to R5RS, both at the level of individual bytes and characters and at the level of S-expressions.
11.2.1 Reading Bytes, Characters, and Strings
In addition to the standard reading procedures, MzScheme provides
byte-reading procedure, block-reading procedures such as
, and more.
read-line
(read-line[input-port mode-symbol])returns a string containing the next line of bytes frominput-port. Ifinput-portis omitted, the current input port is used.Characters are read from
input-portuntil a line separator or an end-of-file is read. The line separator is not included in the result string (but it is removed from the port's stream). If no characters are read before an end-of-file is encountered,is returned.eofThe
mode-symbolargument determines the line separator(s). It must be one of the following symbols:'linefeedbreaks lines on linefeed characters; this is the default.'returnbreaks lines on return characters.'return-linefeedbreaks lines on return-linefeed combinations. If a return character is not followed by a linefeed character, it is included in the result string; similarly, a linefeed that is not preceded by a return is included in the result string.'anybreaks lines on any of a return character, linefeed character, or return-linefeed combination. If a return character is followed by a linefeed character, the two are treated as a combination.'any-onebreaks lines on either a return or linefeed character, without recognizing return-linefeed combinations.
Return and linefeed characters are detected after the conversions that are automatically performed when reading a file in text mode. For example, reading a file in text mode under Windows automatically changes return-linefeed combinations to a linefeed. Thus, when a file is opened in text mode,
'linefeedis usually the appropriatemode.read-line(read-bytes-line[input-port mode-symbol])is analogous toread-line, but it reads bytes and produces a byte string.(read-stringk[input-port])returns a string containing the nextkcharacters frominput-port. The default value ofinput-portis the current input port.If
kis0, then the empty string is returned. Otherwise, if fewer thankcharacters are available before an end-of-file is encountered, then the returned string will contain only those characters before the end-of-file (i.e., the returned string's length will be less thank). 34 If no characters are available before an end-of-file, thenis returned.eofIf an error occurs during reading, some characters may be lost (i.e., if
successfully reads some characters before encountering an error, the characters are dropped.)read-string(read-bytesk[input-port])is analogous toread-string, but it reads bytes and produces a byte string.(read-string!string[input-port start-k end-k])reads characters frominput-portlikeread-string, but puts them intostringstarting from indexstart-k(inclusive) up toend-k(exclusive). The default value ofinput-portis the current input port. The default value ofstart-kis0. The default value ofend-kis the length of thestring. Like, thesubstringexn:fail:contractexception is raised ifstart-korend-kis out-of-range forstring.If the difference between
start-kandend-kis0, then0is returned andbytesis not modified. If no bytes are available before an end-of-file, thenis returned. Otherwise, the return value is the number of bytes read. Ifeofmbytes are read andm<end-k-start-k, thenbytesis not modified at indicesstart-k+mthoughend-k.(read-bytes!string[input-port start-k end-k])is analogous toread-string!, but it reads bytes and puts them into a byte string.(read-bytes-avail!bytes[input-port start-k end-k])is likeread-bytes!, but returns without blocking after reading immediately-available bytes, and it may return a procedure for a ``special'' result. Theprocedure blocks only if no bytes (or specials) are yet available. Also unlikeread-bytes-avail!read-bytes!,never drops bytes; ifread-bytes-avail!successfully reads some bytes and then encounters an error, it suppresses the error (treating it roughly like an end-of-file) and returns the read bytes. (The error will be triggered by future reads.) If an error is encountered before any bytes have been read, an exception is raised.read-bytes-avail!When
input-portproduces a special value, as described in section 11.1.7, the result is a procedure of four arguments. The four arguments correspond to the location of the special value within the port, as described in section 11.1.7. If the procedure is called more than once with valid arguments, theexn:fail:contractexception is raised. Ifread-bytes-availreturns a special-producing procedure, then it does not place characters inbytes. Similarly,read-bytes-availplaces only as many bytes intobytesas are available before a special value in the port's stream.(read-bytes-avail!*bytes[input-port start-k end-k])is like, except that it returnsread-bytes-avail!0immediately if no bytes (or specials) are available for reading and the end-of-file is not reached.(read-bytes-avail!/enable-breakbytes[input-port start-k end-k])is like, except that breaks are enabled during the read (see also section 6.7). If breaking is disabled whenread-bytes-avail!is called, and if theread-bytes-avail!/enable-breakexn:breakexception is raised as a result of the call, then no bytes will have been read frominput-port.(peek-stringk skip-k[input-port])is similar toread-string, except that the returned characters are preserved in the port for future reads. (More precisely, undecoded bytes are left for future reads.) Theskip-kargument indicates a number of bytes (not characters) in the input stream to skip before collecting characters to return; thus, in total, the nextskip-kbytes pluskcharacters are inspected.For most kinds of ports, inspecting
skip-kbytes andkcharacters requires at leastskip-k+kbytes of memory overhead associated with the port, at least until the bytes/characters are read. No such overhead is required when peeking into a string port (see section 11.1.5), a pipe port (see section 11.1.4), or a custom port with a specific peek procedure (depending on how the peek procedure is implemented; see section 11.1.7).If a port produces
mid-stream, peek skips beyond theeofalways produceeofuntil theeofis read.eof(peek-bytesk skip-k[input-port])is analogous topeek-string, but it peeks bytes and produces a byte string.(peek-string!string skip-k[input-port start-k end-k])is likeread-string!, but for peeking, and with askip-kargument like.peek-string(peek-bytes!bytes skip-k[input-port start-k end-k])is analogous topeek-string!, but it peeks bytes and puts them into a byte string.(peek-bytes-avail!bytes skip-k[progress-evt input-port start-k end-k])is like, but for peeking, and with two extra arguments. Theread-bytes-avail!skip-kargument is as in. Thepeek-bytesprogress-evtargument must be either#f(the default) or an event produced byforport-progress-evtinput-port.To peek,
blocks until finding an end-of-file, at least one byte (or special) past the skipped bytes, or until a non-peek-bytes-avail!#fprogress-evtbecomes ready. Furthermore, ifprogress-evtis ready before bytes are peeked, no bytes are peeked or skipped, andprogress-evtmay cut short the skipping process if it becomes available during the peek attempt.The result of
ispeek-bytes-avail!0only in the case thatprogress-evtbecomes ready before bytes are peeked.(peek-bytes-avail!*bytes skip-k[progress-evt input-port start-k end-k])is like, but for peeking, and withread-bytes-avail!*