Chapter 3 - Essential Lisp in Twelve Lessons

Lesson 11 - Essential Input and Output

READ accepts Lisp data

As we saw in Lesson 10, READ turns characters into Lisp data. So far, you've seen a printed representation of several kinds of Lisp data:

The Lisp reader does its job according to a classification of characters. The standard classifications are shown below. As we'll see in Lesson 12, you can alter these classifications for your own needs.

Standard Constituent Characters
 a b c d e f g h i j k l m n o p q r s t u v w x y z
 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
 0 1 2 3 4 5 6 7 8 9
 ! $ % & * + - . / : < = > ? @ [ ] ^ _ { } ~
 <backspace> <rubout>
Standard Terminating Macro Characters
 " ' ( ) , ; `
Standard Non-Terminating Macro Characters
Standard Single Escape Characters
Standard Multiple Escape Characters
Standard Whitespace Characters
 <tab> <space> <page> <newline> <return> <linefeed>

If READ starts with a constituent character, it begins accumulating a symbol or number. When READ encounters a terminating macro character or a whitespace character, it tries to interpret the collected constituent characters first as a number, then as a symbol. If a numeric interpretation is possible, READ returns the number. Otherwise, READ changes the alphabetical characters to a standard case (normally upper case), interns the name as a symbol, and returns the symbol.

Escape characters play a special role. A single escape character forces the following character to be treated exactly as a constituent character. In this way characters that are normally treated as whitespace or terminating macro characters can be part of a symbol. If READ encounters an escape character, it never attempts to interpret the resulting constituents as a number, even if only digits were escaped.

If READ starts with a macro character, the character determines the next step:

Read a string.
Read a form.
Read a list.
Ignore everything up to newline.
Decide what to do based on the next character.

Finally, some Lisp data is not meant to be read. For example, the printed representation of a hash table looks something like #<HASH-TABLE>. It is an error for READ to attempt to read anything beginning with the characters #<.

PRINT writes Lisp data for you and for READ

The PRINT function changes a Lisp object into the sequence of characters that READ would need to reconstruct it:

? (print 'abc)
=> ABC

? (print (list 1 2 3))
=> CR(1 2 3)SPC
=> (1 2 3)

? (print "A String")
=> CR"A string"SPC
=> "A string"

? (print 387.9532)
=> CR387.9532SPC
=> 387.9532

? (print (make-hash-table))

PRINT always begins its output with a newline character (CR), and follows its output with a space (SPC). This ensures that the PRINT output stands apart from any surrounding output, since newline and space are both treated as whitespace, and cannot be part of the printed representation of a Lisp object (unless escaped).

Other variations of PRINT have different uses. PRIN1 behaves as PRINT, but does not surround its output with whitespace. This might be useful if you are building up a name from successive pieces, for example. PRINC behaves as PRIN1, but generates output intended for display, rather than READ; for example, PRINC omits the quotes around a string, and does not print escape characters.

? (print 'a\ bc)
=> |A BC|

? (prin1 'a\ bc)
=> |A BC|
=> |A BC|

? (princ '|A BC|)
=> |A BC|

OPEN and CLOSE let you work with files

Normally, READ reads from the keyboard and PRINT prints to the screen. Both of these functions take an optional argument; the argument specifies an input stream for READ, and an output stream for PRINT. What's a stream? A stream is a source or sink of data, typically -- but not necessarily -- characters. For now, we'll look at how text files can be the source or sink of a character stream. In Chapter 19 we'll look at some of the other possibilities.

You can attach a stream to a file using the OPEN function, which takes as parameters a file name and a keyword argument to specify the direction (input or output) of the stream. To finish operations on the stream and close the associated file, use the CLOSE function.

? (setq out-stream (open "my-temp-file" :direction :output))
-> #<OUTPUT-STREAM "my-temp-file">

? (print 'abc out-stream)
-> ABC

? (close out-stream)
-> T

? (setq in-stream (open "my-temp-file" :direction :input))
-> #<INPUT-STREAM "my-temp-file">

? (read in-stream)
-> ABC

? (close in-stream)
-> T

In the example, we create an output stream to the file named my-temp-file, and print the symbol ABC to that stream. Notice how PRINT returns its argument as usual, but doesn't print it -- the printed result has gone to the file, instead.

Next, we close the output stream and open an input stream on the same file. We then read the symbol that we printed to the file, and finish by closing the input stream.

Variations on a PRINT theme

Lisp also provides a WRITE function to give you control over more details of printing, using keyword arguments to control these options:

Keyword Argument   Default Value           Action
----------------   -------------           ------
 :stream           t                       set output stream
 :escape           *print-escape*          include escape characters
 :radix            *print-radix*           include radix (base) prefix
 :base             *print-base*            set number base (rationals)
 :circle           *print-circle*          print circular structures
 :pretty           *print-pretty*          add whitespace for readability
 :level            *print-level*           limit nesting depth
 :length           *print-length*          limit items per nesting level
 :case             *print-case*            :upper, :lower, or :mixed
 :gensym           *print-gensym*          prefix uninterned symbols
 :array            *print-array*           print arrays readably
 :readably         *print-readably*        force printing to be readable
 :right-margin     *print-right-margin*    controls pretty-printing
 :miser-width      *print-miser-width*                "
 :lines            *print-lines*                      "
 :pprint-dispatch  *print-pprint-dispatch*            "

Coincidentally, the variables named above as the default values of the keyword arguments also control the operation of PRINT. You can get the effect of WRITE with non-default keyword arguments by binding these variables in a LET form around a PRIN1:

(write foo                    (let ((*print-pretty* t)
   :pretty t                        (*print-right-margin* 60)
   :right-margin 60                 (*print-case* :downcase))
   :case :downcase)      EQV      (prin1 foo)) 

We used PRIN1 rather than PRINT because we don't want the preceding newline and following blank that PRINT adds.

If your program changes the *PRINT-...* variables, but you need to ensure the default values at some point in your program, you can wrap that part of the program inside a WITH-STANDARD-IO-SYNTAX form:

;Define printer control for the program.
(setq *print-circle* t)
(setq *print-array* nil)
(setq *print-escape* nil)
;Print with the settings established above.
(print ...)
;Change back to default printer control settings
   ;Print with the standard settings,
   ;overriding those established above.
   (print ...)
;Outside the WITH-STANDARD-IO-SYNTAX form,
;we once again have the print settings established
;by the SETQ forms at the top of the example.

Contents | Cover
Chapter 2 | Chapter 3, Introduction | Chapter 3, Lesson 10 | Chapter 3, Lesson 11 | Chapter 3, Lesson 12 | Chapter 4 |

Copyright © 1995-2001, David B. Lamkins
All Rights Reserved Worldwide

This book may not be reproduced without the written consent of its author. Online distribution is restricted to the author's site.