next: Concurrency previous: Libraries top: Contents
Data input and output is done on ports. A port can be associated with consoles, files and strings. When a port is provided to a character-based operation, such as read, the port’s bytes are read and interpreted as UTF-8 encoded characters. 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 631 so that the entire sequence of bytes is a valid UTF-8 encoding.
The following predicates will be useful while working I/O ports:
-
(port? v) returns #t if either (input-port? v) or (output-port? v) is #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, #f otherwise.
-
(terminal-port? port) returns #t if the given port is attached to an interactive terminal, #f otherwise.
The procedures current-input-port and current-output-port returns the currently active input and output ports.
Spark's input and output procedures take an optional port argument. If the port is not specified, they read/write to the standard ports. It means both,
(display "hello, world") (display "hello, world" (current-output-port))
has the same behavior.
End-of-file
The global variable eof is bound to the end-of-file value. The standard 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.
File Ports
The open-input-file and open-output-file procedures are used to open input and output files respectivley. They 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.
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:
-
'error - raise exn:fail:filesystem (this is the default)
-
'replace - remove the old file and write a new one
-
'truncate - overwrite the old data
-
'truncate/replace - try 'truncate; if it fails, try 'replace
-
'append - append to the end of the file.
-
'update—open an existing file without truncating it; if the file does not exist, the exn:fail:filesystem exception is raised
The open-input-output-file procedure takes the same arguments as open-output-file, 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.
The following code opens a text file and print its contents, one character at a time:
(define file (open-input-file "test.txt" 'text))
(let loop ((c (read-char file)))
(if (not (eof-object? c))
(begin
(write-char c)
(loop (read-char file)))))
(close-input-port file)
A more efficient way for reading files is to read them in chunks:
(define file (open-input-file "test.txt" 'text))
(define chunk-size 1024)
(let loop ((c (read-string chunk-size file)))
(if (not (eof-object? c))
(begin
(display c)
(loop (read-string chunk-size file)))))
(close-input-port file)
read-bytes can be used to read in the data as bytes instead of UTF-8 encoded characters.
Writing to files are as simple as reading from them. To write formatted strings to an output port we can use fprintf which behaves the same as printf.
(define file (open-output-file "test.txt" 'text)) (write 10 file) (newline file) (write-char #\a file) (newline file) (fprintf file "This is a formatted output. ~X ~n" 255) (close-output-port file)
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. All optinal arguments of open-output-file and open-input-file can be used with these two procedures as well. It is guarenteed that the file port will be closed, even if the control jumps out the lambda expression. This can happen as a result of an excpetion or call to a continuation. If the control jumps back to the procedure, the file port will remain closed.
String ports
It is possible to do formatted I/O on string by opening ports on them. Use the open-output-string and open-input-string procedures for this.
> (define s (open-output-string)) > (fprintf s "1045 in hex is 0x~X" 1045) > (get-output-string s) ;; Returns a new string object with the port's contents. "1045 in hex is 0x415"
Loading source files
We have already seen the procedure load that loads files containing Spark code. Loading a file consists in evaluating in sequence every expression in the file. The pathname argument given to load is reckoned relative to the current working directory of Spark, which is normally the directory in which the Spark 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 Spark’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.
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 Spark’s current directory, and thus allows convenient multifile program development.
