Chapter 8. Input/Output Operations

This chapter describes Chez Scheme's generic port facility, operations on ports, and various Chez Scheme extensions to the standard set of input/output operations. See Chapter 7 of The Scheme Programming Language, Second Edition or the Revised5 Report on Scheme for a description of standard input/output operations. Definitions of a few sample generic ports are given in Section 8.10.


Section 8.1. Generic Ports

Chez Scheme's "generic port" facility allows the programmer to add new types of ports with arbitrary input/output semantics. It may be used, for example, to define any of the built-in Common Lisp [22] stream types, i.e., synonym streams, broadcast streams, concatenated streams, two-way streams, echo streams, and string streams. It may also be used to define more exotic ports, such as ports that represent windows on a bit-mapped display or ports that represent processes connected to the current process via pipes or sockets.

Each port has an associated port handler. A port handler is a procedure that accepts messages in an object-oriented style. Each message corresponds to one of the low-level Scheme operations on ports, such as read-char and close-input-port (but not read, which is defined in terms of the lower-level operations). Most of these operations simply call the handler immediately with the corresponding message.

Standard messages adhere to the following conventions: the message name is the first argument to the handler. It is always a symbol, and it is always the name of a primitive Scheme operation on ports. The additional arguments are the same as the arguments to the primitive procedure and occur in the same order. (The port argument to some of the primitive procedures is optional; in the case of the messages passed to a handler, the port argument is always supplied.) The following messages are defined for built-in ports:

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

Additional messages may be accepted by user-defined ports.

Chez Scheme input and output is normally buffered for efficiency. To support buffering, each input port contains an input buffer and each output port contains an output buffer. Bidirectional ports, ports that are both input ports and output ports, contain both input and output buffers. Input is not buffered if the input buffer is the empty string, and output is not buffered if the output buffer is the empty string. In the case of unbuffered input and output, calls to read-char, write-char, and similar messages cause the handler to be invoked immediately with the corresponding message. For buffered input and output, calls to these procedures cause the buffer to be updated, and the handler is not called under normal circumstances until the buffer becomes empty (for input) or full (for output). Handlers for buffered ports must not count on the buffer being empty or full when read-char, write-char, and similar messages are received, however, due to the possibility that (a) the handler is invoked through some other mechanism, or (b) the call to the handler is interrupted.

In the presence of keyboard, timer, and other interrupts, it is possible for a call to a port handler to be interrupted or for the handler itself to be interrupted. If the port is accessible outside of the interrupted code, there is a possibility that the interrupt handler will cause input or output to be performed on the port. This is one reason, as stated above, that port handlers must not count on the input buffer being empty or output buffer being full when a read-char, write-char, or similar message is received. In addition, port handlers may need to manipulate the buffers only within critical sections (using critical-section).

Generic ports are created via one of the three port construction procedures make-input-port, make-output-port, and make-input/output-port, which are defined later in this chapter. Ports have seven accessible fields:

handler,
accessed with port-handler;
output-buffer,
accessed with port-output-buffer,
output-size,
accessed with port-output-size,
output-index,
accessed with port-output-index,
input-buffer,
accessed with port-input-buffer,
input-size,
accessed with port-input-size, and
input-index,
accessed with port-input-index.

The output-size and output-index fields are valid only for output ports, and the input-size and input-index fields are valid only for input ports. The output and input size and index fields may be updated as well using the corresponding "set-field!" procedure.

A port's output size determines how much of the port's output buffer is actually available for writing by write-char. The output size is often the same as the string length of the port's output buffer, but it can be set to less (but no less than zero) at the discretion of the programmer. The output index determines to which position in the port's buffer the next character will be written. The output index should be between 0 and the output size, inclusive. If no output has occurred since the buffer was last flushed, the output index should be 0. If the index is less than the size, write-char stores its character argument into the specified character position within the buffer and increments the index. If the index is equal to the size, write-char leaves the fields of the port unchanged and invokes the handler.

A port's input size determines how much of the port's input buffer is actually available for reading by read-char. A port's input size and input index are constrained in the same manner as output size and index, i.e., the input size must be between 0 and the string length of the input buffer (inclusive), and the input index must be between 0 and the input size (inclusive). Often, the input size is less than the length of the input buffer because there are fewer characters available to read than would fit in the buffer. The input index determines from which position in the input buffer the next character will be read. If the index is less than the size, read-char extracts the character in this position, increments the index, and returns the character. If the index is equal to the size, read-char leaves the fields of the port unchanged and invokes the handler.

The operation of peek-char is similar to that of read-char, except that it does not increment the input index. unread-char decrements the input index if it is greater than 0, otherwise it invokes the handler. char-ready? returns #t if the input index is less than the input size, otherwise it invokes the handler.

Although the fields shown and discussed above are logically present in a port, actual implementation details may differ. The current Chez Scheme implementation uses a different representation that allows read-char, write-char, and similar operations to be open-coded with minimal overhead. The access and assignment operators perform the conversion between the actual representation and the one shown above.

Port handlers receiving a message must return a value appropriate for the corresponding operation. For example, a handler receiving a read-char message must return a character or eof object (if it returns). For operations that return unspecified values, such as close-port, the handler is not required to return any particular value.


Section 8.2. Port Operations

The procedures used to create, access, and alter ports directly are described in this section. Also described are several nonstandard operations on port.

Unless otherwise specified, procedures requiring either input ports or output ports as arguments accept input/output ports as well, i.e., an input/output port is both an input port and an output port.


procedure: (make-input-port handler input-buffer)
procedure: (make-output-port handler output-buffer)
procedure: (make-input/output-port handler input-buffer output-buffer)
returns: a new port

handler must be a procedure, and input-buffer and output-buffer must be strings. Each procedure creates a generic port. The handler associated with the port is handler, the input buffer is input-buffer, and the output buffer is output-buffer. For make-input-port, the output buffer is undefined, and for make-output-port, the input buffer is undefined.

The input size of an input or input/output port is initialized to the string length of the input buffer, and the input index is set to 0. The output size and index of an output or input/output port are initialized similarly.

The length of an input or output buffer may be zero, in which case buffering is effectively disabled.


procedure: (port-handler port)
returns: a procedure

For generic ports, port-handler returns the handler passed to one of the generic port creation procedures described above. For ports created by open-input-file and similar procedures, port-handler returns an internal handler that may be invoked in the same manner as any other handler.


procedure: (port-input-buffer input-port)
procedure: (port-input-size input-port)
procedure: (port-input-index input-port)
returns: see below

These procedures return the input buffer, size, or index of input-port.


procedure: (set-port-input-size! input-port n)
procedure: (set-port-input-index! input-port n)
returns: unspecified

The procedure set-port-input-index! sets the input index field of input-port to n, which must be a nonnegative integer less than or equal to the port's input size.

The procedure set-port-input-size! sets the input size field of input-port to n, which must be a nonnegative integer less than or equal to the string length of the port's input buffer.

The procedure set-port-input-size! also sets the input index to 0.


procedure: (port-output-buffer output-port)
procedure: (port-output-size output-port)
procedure: (port-output-index output-port)
returns: see below

These procedures return the output buffer, size, or index of output-port.


procedure: (set-port-output-size! output-port n)
procedure: (set-port-output-index! output-port n)
returns: unspecified

The procedure set-port-output-index! sets the output index field of output-port to n, which must be a nonnegative integer less than or equal to the port's output size.

The procedure set-port-output-size! sets the output size field of output-port to n, which must be a nonnegative integer less than or equal to the string length of the port's output buffer.

The procedure set-port-output-size! also sets the output index to 0.


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

This procedure directly marks the port closed so that no further input or output operations are allowed on it. It is typically used by handlers upon receipt of a close-port message.


procedure: (port? obj)
returns: #t if obj is a port, #f otherwise

(port? "hi there")  #f
(port? (current-input-port))  #t
(port? (current-output-port))  #t


procedure: (close-port port)
returns: unspecified

The procedure close-port is equivalent to close-input-port on an input or input/output port, and to close-output-port on an output or input/output port.

Chez Scheme closes ports automatically after they become inaccessible to the program or when the Scheme program exits, but it is best to close ports explicitly whenever possible.


procedure: (port-closed? port)
returns: #t if port is closed, #f otherwise

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

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


procedure: (port-name port)
returns: the name associated with port

The name may be a string or #f (denoting no name). For file ports, the name is typically a string naming the file.

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

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


procedure: (file-length port)
returns: the length of the file to which port refers

The length is measured in bytes from the start of the file.


procedure: (file-position port)
procedure: (file-position port n)
returns: see below

When the second argument is omitted, file-position returns the position of the port in the file to which the port refers, measured in bytes from the start of the file. For input ports, this is the position from which the next character will be read, and for output ports, this is the position to which the next character will be written. If the file position cannot be determined, the most negative fixnum is returned.

When passed two arguments, file-position sets the file position of port to n. Normally, the maximum allowable value for the second argument to file-position is the value returned by file-length, although some operating systems automatically extend output files when the value of the second argument exceeds this amount.


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

If input-port is not supplied, it defaults to the current input port. This procedure discards any characters in the buffer associated with input-port. This may be necessary, for example, to clear any type-ahead from the keyboard in preparation for an urgent query.


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

If output-port is not supplied, it defaults to the current output port. This procedure discards any characters in the buffer associated with output-port. This may be necessary, for example, to clear any pending output on an interactive port in preparation for an urgent message.


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

If output-port is not supplied, it defaults to the current output port. This procedure forces any characters in the buffer associated with output-port to be printed immediately. The console output port is automatically flushed after a newline and before input from the console input port; all ports are automatically flushed when they are closed. flush-output-port may be necessary, however, to force a message without a newline to be sent to the console output port or to force output to appear on a file without delay.


Section 8.3. String Ports

String ports allow the creation and manipulation of strings via port operations. make-input-string converts a string into an input port, allowing the characters in the string to be read in sequence via input operations such as read-char or read. make-output-string allows new strings to be built up with output operations such as write-char and write.

While string ports could be defined as generic ports, they are instead supported as primitive by the implementation.


procedure: (open-input-string string)
returns: a new string input port

A string input port is similar to a file input port, except that characters and objects drawn from the port come from string rather than from a file.

A string port is at "end of file" when the port reaches the end of the string. It is not necessary to close a string port, although it is okay to do so.

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


procedure: (open-output-string)
returns: a new string output port

A string output port is similar to a file output port, except that characters and objects written to the port are placed in a string (which grows as needed) rather than to a file.

The string built by writing to a string output port may be obtained with get-output-string. See the example given for get-output-string below. It is not necessary to close a string port, although it is okay to do so.


procedure: (get-output-string string-output-port)
returns: the string associated with string-output-port

As a side effect, get-output-string resets string-output-port so that subsequent output to string-output-port is placed into a fresh string.

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

See the definition of format in Section 8.6 for a more elaborate example of the use of string ports.


Section 8.4. Input Operations


parameter: console-input-port

console-input-port is a parameter that determines the input port used by the waiter and interactive debugger. When called with no arguments, it returns the console input port. When called with an input port argument, it changes the value of the console input port.


parameter: current-input-port

current-input-port is a parameter that determines the default port argument for most input procedures, including read-char, peek-char, and read, When called with no arguments, current-input-port returns the current input port. When called with an input port argument, current-input-port changes the value of the current input port. The standard Scheme version of current-input-port accepts no arguments, i.e., it cannot be used to change the current input port.


procedure: (unread-char char)
procedure: (unread-char char input-port)
returns: unspecified

If input-port is not supplied, it defaults to the current input port. unread-char "unreads" the last character read from input-port. char may or may not be ignored, depending upon the implementation. In any case, it is an error for char not to be last character read from the port. It is also an error to call unread-char twice on the same port without an intervening call to read-char.

unread-char is provided for applications requiring one character of lookahead and may be used in place of, or even in combination with, peek-char. One character of lookahead is required in the procedure read-word, which is defined below in terms of unread-char. read-word returns the next word from an input port as a string, where a word is defined to be a sequence of alphabetic characters. Since it does not know until it reads one character too many that it has read the entire word, read-word uses unread-char to return the character to the input port.

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

In the alternate version below, peek-char is used instead of unread-char.

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

The advantage of unread-char in this situation is that only one call to unread-char per word is required, whereas one call to peek-char is required for each character in the word plus the first character beyond. In many cases, unread-char does not enjoy this advantage, and peek-char should be used instead.


procedure: (block-read input-port string count)
returns: see below

count must be a nonnegative fixnum less than or equal to the length of string.

If input-port is at end-of-file, an eof object is returned. Otherwise, string is filled with as many characters as are available for reading from input-port up to count, and the number of characters placed in the string is returned.

If input-port is buffered and the buffer is nonempty, the buffered input or a portion thereof is returned; otherwise block-read bypasses the buffer entirely.


procedure: (read-token)
procedure: (read-token input-port)
returns: see below

Parsing of a Scheme datum is conceptually performed in two steps. First, the sequence of characters that form the datum are grouped into tokens, such as symbols, numbers, left parentheses, and double quotes. During this first step, whitespace and comments are discarded. Second, these tokens are grouped into data.

read performs both of these steps and creates an internal representation of each datum it parses. read-token may be used to perform the first step only, one token at a time. read-token is intended to be used by editors and program formatters that must be able to parse a program or datum without actually reading it.

If input-port is not supplied, it defaults to the current input port. One token is read from the input port and returned as four values:

type:
a symbol describing the type of token read,

value:
the token value,

start:
the position of the first character of the token, relative to the starting position of the input port, and

end:
the first position beyond the token, relative to the starting position of the input port.

When the token type fully specifies the token, read-token returns #f for the value. The token types are listed below with the corresponding value in parentheses.

atomic
(atom) an atomic value, i.e., a symbol, boolean, number, character, #!eof, or #!bwp
box
(#f) box prefix, i.e., #&
dot
(#f) dotted pair separator, i.e., .
eof
(#!eof) end of file
fasl
(#f) fasl prefix, i.e., #@
insert
(n) graph reference, i.e., #n#
lbrace
(#f) open brace
lbrack
(#f) open square bracket
lparen
(#f) open parenthesis
mark
(n) graph mark, i.e., #n=
quote
(quote, quasiquote, syntax, unquote, or unquote-splicing) an abbreviation mark, e.g., ' or ,@
record-brack
(#f) record open bracket, i.e., #[
rbrace
(#f) close brace
rbrack
(#f) close square bracket
rparen
(#f) close parenthesis
symbol-brace
(#f) extended symbol open brace, i.e., #{
vnparen
(n) vector prefix, i.e., #n(
vparen
(#f) vector prefix, i.e., #(

The set of token types is likely to change in future releases of the system; check the release notes for details on such changes.

The input port is left pointing to the first character position beyond the token, i.e., end characters from the starting position.

> (read-token)(
lparen
#f
0
1
> (read-token) abc
atomic
abc
1
4
> (read-token (open-input-string ""))
eof
#!eof
0
0
> (define s (open-input-string "#7=#7#"))
> (read-token s)
mark
7
0
3
> (read-token s)
insert
7
3
6

The information read-token returns is not always sufficient for reconstituting the exact sequence of characters that make up a token. For example, 1.0 and 1e0 both return type atomic with value 1.0. The exact sequence of characters may be obtained only by repositioning the port and reading a block of characters of the appropriate length, using the relative positions given by start and end.


Section 8.5. Output Operations


parameter: console-output-port

console-output-port is a parameter that determines the output port used by the waiter and interactive debugger. When called with no arguments, it returns the console output port. When called with an output port argument, it changes the value of the console output port.


parameter: current-output-port

current-output-port is a parameter that determines the default port argument for most output procedures, including write-char, newline, write, display, and pretty-print. When called with no arguments, current-output-port returns the current output port. When called with an output port argument, current-output-port changes the value of the current output port. The standard Scheme version of current-output-port accepts no arguments, i.e., it cannot be used to change the current output port.


procedure: (open-output-file filename)
procedure: (open-output-file filename if-exists)
returns: a new output port

filename must be a string. if-exists, if present, determines what open-output-file does when the file named by filename already exists as follows:

The standard Scheme version of open-output-file does not support the optional if-exists argument.


procedure: (call-with-output-file filename proc)
procedure: (call-with-output-file filename proc if-exists)
returns: the result of invoking proc

filename must be a string. proc must be a procedure of one argument.

call-with-output-file creates a new output port for the file named by filename and passes this port to proc. An error is signaled if the file cannot be opened for output. If proc returns, call-with-output-file closes the output port and returns the value returned by proc.

call-with-output-file does not automatically close the output port if a continuation created outside of proc is invoked, since it is possible that another continuation created inside of proc will be invoked at a later time, returning control to proc. If proc does not return, an implementation is free to close the output port only if it can prove that the output port is no longer accessible. As shown in Section 5.6 of The Scheme Programming Language, Second Edition, dynamic-wind may be used to ensure that the port is closed if a continuation created outside of proc is invoked.

See open-output-file above for a description of the optional if-exists argument. The standard Scheme version of call-with-output-file does not support the optional if-exists argument.


procedure: (with-output-to-file filename thunk)
procedure: (with-output-to-file filename thunk if-exists)
returns: the value returned by thunk

filename must be a string. with-output-to-file temporarily rebinds the current output port to be the result of opening the file named by filename for output during the application of thunk. If thunk returns, the port is closed and the current output port is restored to its old value.

The behavior of with-output-to-file is unspecified if a continuation created outside of thunk is invoked before thunk returns. An implementation may close the port and restore the current output port to its old value---but it may not.

See open-output-file above for a description of the optional if-exists argument. The standard Scheme version of with-output-to-file does not support the optional if-exists argument.


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

count must be a nonnegative fixnum less than or equal to the length of string.

block-write writes the first count characters of string to output-port. If output-port is buffered and the buffer is nonempty, the buffer is flushed before the contents of string are written. In any case, the contents of string are written immediately, without passing through the buffer.


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

If output-port is not supplied, it defaults to the current output port.

pretty-print is similar to write except that it uses any number of spaces and newlines in order to print obj in a style that is pleasing to look at and which shows the nesting level via indentation. For example,

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

might produce

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


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

ifn and ofn must be strings. pretty-file reads each object in turn from the file named by ifn and pretty prints the object to the file named by ofn. Comments present in the input are discarded by the reader and so do not appear in the output file. If the file named by ofn already exists, it is replaced.


Section 8.6. Formatted Output


procedure: (format format-string obj ...)
returns: formatted output string

format constructs an output string from format-string and the objects obj .... Characters are copied from format-string to the output string from left to right, until format-string is exhausted. During this operation, if format encounters a two-character sequence of the form "~x" (tilde followed by the character x), this sequence is replaced in the output string as follows:

format is similar to but less powerful than C's sprintf and Common Lisp's format.

An error is signaled if more or fewer objs are given than required by format-string, or if format-string ends in a tilde.

(format "hi there")  "hi there"

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

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

(format "~s.~s" 3 4)  "3.4"

(format "~s" 12345)  "12345"

(format "line one,~%line two.")  "line one,
                                 line two."

format could be defined roughly as follows, with error checking omitted to simplify the code.

(define format
  (lambda (format-string . objects)
    (let ([ip (open-input-string format-string)])
      (let ([op (open-output-string)])
        (let f ([c (read-char ip)] [ls objects])
          (cond
            [(eof-object? c)
             (get-output-string op)]
            [(char=? c #\~)
             (case (read-char ip)
               [(#\s)
                (write (car ls) op)
                (f (read-char ip) (cdr ls))]
               [(#\a)
                (display (car ls) op)
                (f (read-char ip) (cdr ls))]
               [(#\c)
                (write-char (car ls) op)
                (f (read-char ip) (cdr ls))]
               [(#\%)
                (newline op)
                (f (read-char ip) ls)]
               [(#\~)
                (write-char #\~ op)
                (f (read-char ip) ls)])]
            [else
             (write-char c op)
             (f (read-char ip) ls)]))))))


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

Rather than returning a formatted string, these procedures write the formatted output to a port. printf always prints to the current output port.


Section 8.7. Input/Output Control Operations

The I/O control operations described in this section are used to control how the reader reads and printer writes, displays, or pretty-prints characters, symbols, uninterned symbols, numbers, vectors, long or deeply nested lists or vectors, and graph-structured objects.


procedure: (char-name obj)
returns: see below
procedure: (char-name name char)
returns: unspecified

char-name is used to associate names (symbols) with characters or to retrieve the most recently associated name or character for a given character or name. A name can map to only one character, but more than one name can map to the same character. The name most recently associated with a character determines how that character prints, and each name associated with a character may be used after the #\ character prefix to name that character on input.

In the one-argument form, obj must be a symbol or character. If it is a symbol and a character is associated with the symbol, char-name returns that character. If it is a symbol and no character is associated with the symbol, char-name returns #f. Similarly, if obj is a character, char-name returns the most recently associated symbol for the character or #f if no name is associated with the character. For example, with the default set of character names:

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

When passed two arguments, name is added to the set of names associated with char, and any other association for name is dropped. char may be #f, in which case any other association for name is dropped and no new association is formed. In either case, any other names associated with char remain associated with char.

The following interactive session demonstrates the use of char-name to establish and remove associations between characters and names, including the association of more than one name with a character.

> (char-name 'etx)
#f
> (char-name 'etx #\003)
> (char-name 'etx)
#\etx
> (char-name #\003)
etx
> #\etx
#\etx
> (eq? #\etx #\003)
#t
> (char-name 'etx #\space)
> (char-name #\003)
#f
> (char-name 'etx)
#\etx
> #\space
#\etx
> (char-name 'etx #f)
> #\etx

Error in read: invalid character name #\etx.
> #\space
#\space


parameter: case-sensitive

The case-sensitive parameter determines whether or not the reader and printer are case-sensitive with respect to symbol names. When set to false (the default, as required by the Scheme standard) the case of alphabetic characters within symbol names is insignificant. When set to true, case is significant.

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


parameter: print-graph

When print-graph is set to a nonfalse value, write and pretty-print locate and print objects with shared structure, including cycles, in a notation that may be read subsequently with read. This notation employs the syntax "#n=obj," where n is a nonnegative integer and obj is the printed representation of an object, to label the first occurrence of obj in the output. The syntax "#n#" is used to refer to the object labeled by n thereafter in the output. print-graph is set to #f by default.

If graph printing is not enabled, the settings of print-length and print-level are insufficient to force finite output, and write or pretty-print detects a cycle in an object it is given to print, a warning is issued and the object is printed as if print-graph were enabled.

Since objects printed through the ~s option in the format control strings of format, printf, and fprintf are printed as with write, the printing of such objects is also affected by print-graph.

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

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

The graph syntax is understood by the procedure read, allowing graph structures to be printed and read consistently.


parameter: print-level
parameter: print-length

When called without arguments, print-level returns the current print level and print-length returns the current print length. When called with one argument, which must be a nonnegative fixnum or #f, print-level sets the current print level and print-level sets the current print length to the argument

When print-level is set to a nonnegative integer n, the procedures write and pretty-print traverse only n levels deep into nested structures. If a structure being printed exceeds n levels of nesting, the substructure beyond that point is replaced in the output by an ellipsis ( ... ). print-level is set to #f by default, which places no limit on the number of levels printed.

When print-length is set to a nonnegative integer n, the procedures write and pretty-print print only n elements of a list or vector, replacing the remainder of the list or vector with an ellipsis ( ... ). print-length is set to #f by default, which places no limit on the number of elements printed.

Since objects printed through the ~s option in the format control strings of format, printf, and fprintf are printed as with write, the printing of such objects is also affected by print-level and print-length.

The parameters print-level and print-length are useful for controlling the volume of output in contexts where only a small portion of the output is needed to identify the object being printed. They are also useful in situations where circular structures may be printed (see also print-graph).

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

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

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

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


parameter: print-radix

The print-radix parameter determines the radix in which numbers are printed by write, pretty-print, and display. Its value should be an integer between 2 and 36, inclusive. Its default value is 10.

When the value of print-radix is not 10, write and pretty-print print a radix prefix before the number (#b for radix 2, #o for radix 8, #x for radix 16, and #nr for any other radix n).

Since objects printed through the ~s and ~a options in the format control strings of format, printf, and fprintf are printed as with write and display, the printing of such objects is also affected by print-radix.

(format "~s" 11242957)  "11242957"

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

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


parameter: print-gensym

When print-gensym is set to a true value, the procedures write and pretty-print include the prefix #: before the names of all uninterned symbols, including those generated with gensym. print-gensym is set to #t by default.

Since objects printed through the ~s option in the format control strings of format, printf, and fprintf are printed as with write, the printing of such objects is also affected by print-gensym.

When printing an object that may contain more than one occurrence of an uninterned symbol, it is useful to set both print-graph and print-gensym to #t, so that multiple occurrences of uninterned symbols are marked accurately.

(let ([g (gensym)])
  (format "~s" (list g g)))  "(#:g0 #:g0)"

(let ([g (gensym)])
  (parameterize ([print-gensym #f])
    (format "~s" (list g g))))  "(g0 g0)"

(let ([g (gensym)])
  (parameterize ([print-graph #t])
    (format "~s" (list g g))))  "(#0=#:g0 #0#)"


parameter: print-brackets

When print-brackets is set to a true value, the pretty printer (see pretty-print) uses square brackets rather than parentheses around certain subexpressions of common control structures, e.g., around let bindings and cond clauses. print-brackets is set to #t by default.

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

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


parameter: print-vector-length

When print-vector-length is set to a true value, write and pretty-print include the length for all vectors between the "#" and open parentheses. This parameter is set to #t by default.

When print-vector-length is set to a true value, write and pretty-print also suppress duplicated trailing elements in the vector to reduce the amount of output. This form is also recognized by the reader.

Since objects printed through the ~s option in the format control strings of format, printf, and fprintf are printed as with write, the printing of such objects is also affected by the setting of print-vector-length.

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

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

(parameterize ([print-vector-length #f])
  (format "~s" (vector 'a 'b 'c 'c 'c)))  "#(a b c c c)"


parameter: pretty-line-length
parameter: pretty-one-line-limit

The value of each of these parameters must be a positive fixnum.

The parameters pretty-line-length and pretty-one-line-limit control the output produced by pretty-print. pretty-line-length determines after which character position (starting from the first) on a line the pretty printer attempts to cut off output. This is a soft limit only; if necessary, the pretty-printer will go beyond pretty-line-length.

pretty-one-line-limit is similar to pretty-line-length, except that it is relative to the first nonblank position on each line of output. It is also a soft limit.


parameter: pretty-initial-indent

The value of this parameter must be a nonnegative fixnum.

The parameter pretty-initial-indent is used to tell pretty-print where on an output line it has been called. If pretty-initial-indent is zero (the default), pretty-print assumes that the first line of output it produces will start at the beginning of the line. If set to a nonzero value n, pretty-print assumes that the first line will appear at character position n and will adjust its printing of subsequent lines.


parameter: pretty-standard-indent

The value of this parameter must be a nonnegative fixnum.

The parameter pretty-standard-indent determines the amount by which pretty-print indents subexpressions of most forms, such as let expressions, from the form's keyword or first subexpression.


parameter: pretty-maximum-lines

The parameter pretty-maximum-lines controls how many lines pretty-print prints when it is called. If set to #f (the default), no limit is imposed; if set to a nonnegative fixnum n, at most n lines are printed.


Section 8.8. Fasl Output

The procedures write and pretty-print print objects in a format that is both human readable and machine-readable with read. An alternative fast loading, or fasl, format may be used that is not human readable but that is both more compact and more quickly processed by read. This format is always used for compiled code generated by compile-file, but it may also be used for arbitrary data objects that need to be written and read quickly, such as small databases encoded with Scheme data structures.

Objects are printed in fasl format with fasl-write. Objects written in fasl format and objects written in the standard human-readable format may be included within the same file; the fasl format for any object begins with the prefix #@, allowing the reader to recognize fasl format objects in the input. Since the reader recognizes and handles objects written in fasl format, no special procedures are needed for reading objects written in fasl format.


procedure: (fasl-write obj)
procedure: (fasl-write obj output-port)
returns: unspecified

If output-port is not supplied, it defaults to the current output port. fasl-write writes the fasl prefix #@ to the output port followed by the fasl representation for obj.

> (define op (open-output-string))
> (fasl-write '(a b c) op)
> (write '(1 2 3) op)
> (define ip (open-input-string (get-output-string op)))
> (read ip)
(a b c)
> (read ip)
(1 2 3)


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

ifn and ofn must be strings. fasl-file may be used to convert a file in human-readable format or mixed human-readable and fasl formats into an equivalent file written in fasl format. fasl-file reads each object in turn from the file named by ifn and writes the fasl format for the object onto the file named by ofn. If the file named by ofn already exists, it is replaced.


Section 8.9. File System Operations


parameter: current-directory

When invoked without arguments, current-directory returns a string representing the the current working directory. Otherwise, the current working directory is changed to the directory specified by the argument, which must be a string representing a valid directory pathname.


procedure: (file-exists? filename)
returns: #t if the file named by filename exists, #f otherwise

filename must be a string.


procedure: (delete-file filename)
returns: unspecified

filename must be a string. delete-file removes the file named by filename.


Section 8.10. Generic Port Examples

This section presents the definitions for three types of generic ports: two-way ports, transcript ports, and process ports.

Two-way ports.  The first example defines make-two-way-port, which constructs an input/output port from a given pair of input and output ports. For example:

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

The port returned by make-two-way-port is both an input and an output port:

(port? p)  #t
(input-port? p)  #t
(output-port? p)  #t

Items read from a two-way port come from the constituent input port, and items written to a two-way port go to the constituent output port:

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

The definition of make-two-way-port is straightforward. To keep the example simple, no local buffering is performed, although it would be more efficient to do so.

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

Most of the messages are passed directly to one of the constituent ports. Exceptions are close-port, which is handled directly by marking the port closed, port-name, which is also handled directly. file-position and file-length are rather arbitrarily passed off to the input port.

Transcript ports.  The next example defines make-transcript-port, which constructs an input/output port from three ports: an input port ip and two output ports, op and tp. Input read from a transcript port comes from ip, and output written to a transcript port goes to op. In this manner, transcript ports are similar to two-way ports. Unlike two-way ports, input from ip and output to op is also written to tp, so that tp reflects both input from ip and output to op.

Transcript ports may be used to define the Scheme procedures transcript-on and transcript-off, or the Chez Scheme procedure transcript-cafe. For example, here is a definition of transcript-cafe:

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

The implementation of transcript ports is significantly more complex than the implementation of two-way ports defined above, primarily because it buffers input and output locally. Local buffering is needed to allow the transcript file to reflect accurately the actual input and output performed in the presence of unread-char, clear-output-port, and clear-input-port. Here is the code:

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

The chosen length of both the input and output ports is the same; this is not necessary. They could have different lengths, or one could be buffered locally and the other not buffered locally. Local buffering could be disabled effectively by providing zero-length buffers.

After we create the port, the input size is set to zero since there is not yet any data to be read. The port output size is set to one less than the length of the buffer. This is done so that write-char always has one character position left over into which to write its character argument. Although this is not necessary, it does simplify the code somewhat while allowing the buffer to be flushed as soon as the last character is available.

Block reads and writes are performed on the constituent ports for efficiency and (in the case of writes) to ensure that the operations are performed immediately.

The call to flush-output-port in the handling of read-char insures that all output written to op appears before input is read from ip. Since block-read is typically used to support higher-level operations that are performing their own buffering, or for direct input and output in support of I/O-intensive applications, the flush call has been omitted from that part of the handler.

Critical sections are used whenever the handler manipulates one of the buffers, to protect against untimely interrupts that could lead to reentry into the handler. The critical sections are unnecessary if no such reentry is possible, i.e., if only one "thread" of the computation can have access to the port.

Process ports.  The final example demonstrates how to incorporate the socket interface defined in Section 3.7 into a generic port that allows transparent communication with subprocesses via normal Scheme input/output operations.

A process port is created with open-process, which accepts a shell command as a string. open-process sets up a socket, forks a child process, sets up two-way communication via the socket, and invokes the command in a subprocess.

The sample session below demonstrates the use of make-process, running and communicating with another Scheme image in the subprocess.

> (define p (open-process "exec scheme"))
> (define s (make-string 1000 #\nul))
> (substring s 0 (block-read p s 1000))
"Chez Scheme Version 6.0
Copyright (c) 1998 Cadence Research Systems

> "
> (pretty-print '(+ 3 4) p)
> (substring s 0 (block-read p s 1000))
"7
> "
> (close-port p)
> (exit)

Since process ports, like transcript ports, are two-way, the implementation is somewhat similar. The main difference is that a transcript port reads from and writes to its subordinate ports, whereas a process port reads from and writes to a socket. When a process port is opened, the socket is created and subprocess invoked, and when the port is closed, the socket is closed and the subprocess is terminated.

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


Chez Scheme User's Guide
© 1998 R. Kent Dybvig
Cadence Research Systems
http://www.scheme.com
Illustrations © 1998 Jean-Pierre Hébert
about this book