#
Chapter 7
I/O

Scheme has input/output (I/O) procedures that will let you read from an input port or write to an output port. Ports can be associated with the console, files or strings.

## 7.1 Reading

Scheme's reader procedures take an optional input port argument. If the port is not specified, the current input port (usually the console) is assumed.

Reading can be character-, line- or s-expression-based. Each time a read is performed, the port's state changes so that the next read will read material following what was already read. If the port has no more material to be read, the reader procedure returns a specific datum called the end-of-file or eof object. This datum is the only value that satisfies the `eof-object?` predicate.

The procedure `read-char` reads the next character from the port. `read-line` reads the next line, returning it as a string (the final newline is not included). The procedure `read` reads the next s-expression.

## 7.2 Writing

Scheme's writer procedures take the object that is to be written and an optional output port argument. If the port is not specified, the current output port (usually the console) is assumed.

Writing can be character- or s-expression-based.

The procedure `write-char` writes the given character (without the `#\`) to the output port.

The procedures `write` and `display` both write the given s-expression to the port, with one difference: `write` attempts to use a machine-readable format and `display` doesn't. Eg, `write` uses double quotes for strings and the `#\` syntax for characters. `display` doesn't.

The procedure `newline` starts a new line on the output port.

## 7.3 File ports

Scheme's I/O procedures do not need a port argument if the port happens to be standard input or standard output. However, if you need these ports explicitly, the zero-argument procedures `current-input-port` and `current-output-port` furnish them. Thus,

    (display 9)
    (display 9 (current-output-port))

have the same behavior.

A port is associated with a file by opening the file. The procedure `open-input-file` takes a filename argument and returns a new input port associated with it. The procedure `open-output-file` takes a filename argument and returns a new output port associated with it. It is an error to open an input file that doesn't exist, or to open an output file that already exists.

After you have performed I/O on a port, you should close it with `close-input-port` or `close-output-port`.

In the following, assume the file `hello.txt` contains the single word `hello`.

    (define i (open-input-file "hello.txt"))

    (read-char i)
    =>  #\h

    (define j (read i))

    j
    =>  ello

Assume the file `greeting.txt` does not exist before the following programs are fed to the listener:

    (define o (open-output-file "greeting.txt"))

    (display "hello" o)
    (write-char #\space o)
    (display 'world o)
    (newline o)

    (close-output-port o)

The file `greeting.txt` will now contain the line:

    hello world

### 7.3.1 Automatic opening and closing of file ports

Scheme supplies the procedures `call-with-input-file` and `call-with-output-file` that will take care of opening a port and closing it after you're done with it.

The procedure `call-with-input-file` takes a filename argument and a procedure. The procedure is applied to an input port opened on the file. When the procedure completes, its result is returned after ensuring that the port is closed.

    (call-with-input-file "hello.txt"
      (lambda (i)
        (let* ((a (read-char i))
               (b (read-char i))
               (c (read-char i)))
          (list a b c))))
    =>  (#\h #\e #\l)

The procedure `call-with-output-file` does the analogous services for an output file.

## 7.4 String ports

It is often convenient to associate ports with strings. Thus, the procedure `open-input-string` associates a port with a given string. Reader procedures on this port will read off the string:

    (define i (open-input-string "hello world"))

    (read-char i)
    =>  #\h

    (read i)
    =>  ello

    (read i)
    =>  world

The procedure `open-output-string` creates an output port that will eventually be used to create a string:

    (define o (open-output-string))

    (write 'hello o)
    (write-char #\, o)
    (display " " o)
    (display "world" o)

You can now use the procedure `get-output-string` to get the accumulated string in the string port `o`:

    (get-output-string o)
    =>  "hello, world"

String ports need not be explicitly closed.

## 7.5 Loading files

We have already seen the procedure `load` that loads files containing Scheme code. Loading a file consists in evaluating in sequence every Scheme form in the file. The pathname argument given to `load` is reckoned relative to the current working directory of Scheme, which is normally the directory in which the Scheme executable was called.

Files can load other files, and this is useful in a large program spanning many files. Unfortunately, unless full pathnames are used, the argument file of a `load` is dependent on Scheme's current directory. Supplying full pathnames is not always convenient, because we would like to move the program files as a unit (preserving their relative pathnames), perhaps to many different machines.

MzScheme provides the `load-relative` procedure that greatly helps in fixing the files to be loaded. `load-relative`, like `load`, takes a pathname argument. When a `load-relative` call occurs in a file `foo.scm`, the path of its argument is reckoned from the directory of the calling file `foo.scm`. In particular, this pathname is reckoned independent of Scheme's current directory, and thus allows convenient multifile program development.