Input and Output

We will see that the input and output of Lisp data is handled using streams. Streams are powerful abstractions that support common libraries of functions for writing to the terminal, files, sockets, and to strings.

In all cases, if an input or output function is called without specifying a stream, the default for input stream is *standard-input* and the default for output stream is *standard-output*. These default streams are connected to the Lisp listener that we discussed in Chapter 2. In the later chapter Knowledge Graph Navigator that supports a user interface, we will again use output streams bound to different scrolling output areas of the application window to write color-hilighted text. The stream formalism is general purpose, covering many common I/O use cases.

The Lisp read and read-line Functions

The function read is used to read one Lisp expression. Function read stops reading after reading one expression and ignores new line characters. We will look at a simple example of reading a file test.dat using the example Lisp program in the file read-test-1.lisp. Both of these files can be found in the directory src/code_snippets_for_book that came bundled with this web book. Start your Lisp program in the src directory. The contents of the file test.dat is:

1 1 2 3
2 4 "the cat bit the rat"
3         read with-open-file

In the function read-test-1, we use the macro with-open-file to read from a file. To write to a file (which we will do later), we can use the keyword arguments :direction :output. The first argument to the macro with-open-file is a symbol that is bound to a newly created input stream (or an output stream if we are writing a file); this symbol can then be used in calling any function that expects a stream argument.

Notice that we call the function read with three arguments: an input stream, a flag to indicate if an error should be thrown if there is an I/O error (e.g., reaching the end of a file), and the third argument is the value that function read should return if the end of the file (or stream) is reached. When calling read with these three arguments, either the next expression from the file test.dat will be returned, or the value nil will be returned when the end of the file is reached. If we do reach the end of the file, the local variable x will be assigned the value nil and the function return will break out of the dotimes loop. One big advantage of using the macro with-open-file over using the open function (which we will not cover) is that the file stream is automatically closed when leaving the code generated by the with-open-file macro. The contents of file read-test-1.lisp is:

1 (defun read-test-1 ()
2   "read a maximum of 1000 expressions from the file 'test.dat'"
3   (with-open-file
4    (input-stream "test.dat" :direction :input)
5    (dotimes (i 1000)
6      (let ((x (read input-stream nil nil)))
7        (if (null x) (return)) ;; break out of the 'dotimes' loop
8        (format t "next expression in file: ~S~%" x)))))

Here is the output that you will see if you load the file read-test-1.lisp and execute the expression (read-test-1):

 1 * (load "read-test-1.lisp")
 2 ;; Loading file read-test-1.lisp ...
 3 ;; Loading of file read-test-1.lisp is finished.
 4 T
 5 * (read-test-1)
 6 next expression in file: 1
 7 next expression in file: 2
 8 next expression in file: 3
 9 next expression in file: 4
10 next expression in file: "the cat bit the rat"
11 NIL

Note: the string “the cat bit the rat” prints as a string (with quotes) because we used a ~S instead of a ~A in the format string in the call to function format.

In this last example, we passed the file name as a string to the macro with-open-file. This is not generally portable across all operating systems. Instead, we could have created a pathname object and passed that instead. The pathname function can take eight different keyword arguments, but we will use only the two most common in the example in the file read-test-2.lisp in the src directory. The following listing shows just the differences between this example and the last:

1   (let ((a-path-name
2           (make-pathname :directory "testdata"
3                          :name "test.dat")))
4     (with-open-file
5      (input-stream a-path-name :direction :input)))

Here, we are specifying that we want to use the file test.dat in the subdirectory testdata. Note: I almost never use pathnames. Instead, I specify files using a string and the character / as a directory delimiter. I find this to be portable for the Macintosh, Windows, and Linux operating systems using all Common Lisp implementations.

The file readline-test.lisp is identical to the file read-test-1.lisp except that we call function readline instead of the function read and we change the output format message to indicate that an entire line of text has been read

1 (defun readline-test ()
2   "read a maximum of 1000 expressions from the file 'test.dat'"
3   (with-open-file
4    (input-stream "test.dat" :direction :input)
5    (dotimes (i 1000)
6      (let ((x (read-line input-stream nil nil)))
7        (if (null x) (return)) ;; break out of the 'dotimes' loop
8        (format t "next line in file: ~S~%" x)))))

When we execute the expression (readline-test), notice that the string contained in the second line of the input file has the quote characters escaped:

1 * (load "readline-test.lisp")
2 ;; Loading file readline-test.lisp ...
3 ;; Loading of file readline-test.lisp is finished.
4 T
5 * (readline-test)
6 next line in file: "1 2 3"
7 next line in file: "4 \"the cat bit the rat\""
8 NIL
9 *

We can also create an input stream from the contents of a string. The file read-from-string-test.lisp is very similar to the example file read-test-1.lisp except that we use the macro with-input-from-string (notice how I escaped the quote characters used inside the test string):

1 (defun read-from-string-test ()
2   "read a maximum of 1000 expressions from a string"
3   (let ((str "1 2 \"My parrot is named Brady.\" (11 22)"))
4     (with-input-from-string
5      (input-stream str)
6      (dotimes (i 1000)
7        (let ((x (read input-stream nil nil)))
8          (if (null x) (return)) ;; break out of the 'dotimes' loop
9          (format t "next expression in string: ~S~%" x))))))

We see the following output when we load the file read-from-string-test.lisp:

 1 * (load "read-from-string-test.lisp")
 2 ;; Loading file read-from-string-test.lisp ...
 3 ;; Loading of file read-from-string-test.lisp is finished.
 4 T
 5 * (read-from-string-test)
 6 next expression in string: 1
 7 next expression in string: 2
 8 next expression in string: "My parrot is named Brady."
 9 next expression in string: (11 22)
10 NIL
11 *

We have seen how the stream abstraction is useful for allowing the same operations on a variety of stream data. In the next section, we will see that this generality also applies to the Lisp printing functions.

Lisp Printing Functions

All of the printing functions that we will look at in this section take an optional last argument that is an output stream. The exception is the format function that can take a stream value as its first argument (or t to indicate *standard-output*, or a nil value to indicate that format should return a string value).

Here is an example of specifying the optional stream argument:

1 * (print "testing")
2 
3 "testing" 
4 "testing"
5 * (print "testing" *standard-output*)
6 
7 "testing" 
8 "testing"
9 *

The function print prints Lisp objects so that they can be read back using function read. The corresponding function princ is used to print for “human consumption”. For example:

1 * (print "testing")
2 
3 "testing" 
4 "testing"
5 * (princ "testing")
6 testing
7 "testing"
8 * 

Both print and princ return their first argument as their return value, which you see in the previous output. Notice that princ also does not print a new line character, so princ is often used with terpri (which also takes an optional stream argument).

We have also seen many examples in this book of using the format function. Here is a different use of format, building a string by specifying the value nil for the first argument:

1 * (let ((l1 '(1 2))
2            (x 3.14159))
3        (format nil "~A~A" l1 x))
4 "(1 2)3.14159"
5 * 

We have not yet seen an example of writing to a file. Here, we will use the with-open-file macro with options to write a file and to delete any existing file with the same name:

1 (with-open-file (out-stream "test1.dat"
2                    :direction :output
3                    :if-exists :supersede)
4        (print "the cat ran down the road" out-stream)
5        (format out-stream "1 + 2 is: ~A~%" (+ 1 2))
6        (princ "Stoking!!" out-stream)
7        (terpri out-stream))

Here is the result of evaluating this expression (i.e., the contents of the newly created file test1.dat in the src directory):

1 % cat test1.dat 
2 
3 "the cat ran down the road" 1 + 2 is: 3
4 Stoking!!

Notice that print generates a new line character before printing its argument.