It is not always appropriate to write a data item in a way that can be read by the Lisp reader. For example, the Lisp reader requires that all character strings are surrounded by double quotes to differentiate them from symbols and to deal with white space and special characters. In some cases, the programmer may wish to write a character string in "human-readable" form, with no quotes and escapes on special characters.
The Gamma writer will produce both kinds of output. The print function will always generate output which can be read by the Lisp reader, including escape characters, quotation marks and buffer and instance special forms. The princ function attempts to make the output as readable as possible to a human, but will not necessarily produce output that can be read by the Gamma reader. The name princ is historical, and can simply be thought of as an alternate form of print. Notice that neither princ nor print will automatically place a carriage return at the end of a line. The programmer must explicitly print a "\n" or make a call to terpri.
Like princ and print, there are two forms of the write function. The write function operates identically to the print function, except that its first argument declares the file handle to which it will write its output. The result of a write function is machine readable, whereas the result of a writec function is intended to be human readable. Notice that neither writec nor write will automatically place a carriage return at the end of a line. The programmer must explicitly print a "\n" or make a call to terpri.
The terpri function will produce a carriage return either to the standard output or to a given file handle. terpri is most commonly used to generate a carriage return in a file that is being written using the write function. If the programmer were to use (write file "\n") then the file would actually contain the four characters "\n", rather than the intended carriage return. terpri will insert a carriage return into the file under any circumstances.
All of the printing functions have a further variant, known as the pretty printing functions. These variants attempt to format the output to an 80-column page, inserting line breaks and white space in order to make the output more readable. The pretty-printing indentation rules are intended to make data structure and program flow more easily understood, and closely follow the pattern used by GNU Emacs in its Lisp indentation mode.
It is common when programming with dynamic lists and arrays, or when constructing inter-related class definitions, to create a data structure which is self-referential, or which contains circular references. For example, it may be useful to have a child class contain a pointer to its parent, and the parent class contain a pointer to its child. In this circumstance, an attempt to print an instance of the child class would cause the Lisp writer to enter an infinite loop if it did not take precautions. In C programs, this circumstance is normally avoided by having a printing routine which understands the child/parent relationship and simply writes them in such a way that the infinite loop is never entered. This carries the problem that each data structure must have its own dedicated printing routine, which necessarily does not preserve a generalized data syntax, and which cannot perfectly represent the child/parent relationship in any but the simplest of cases.
Gamma solves the problems of self-reference and circularity by modifying the printed representation of an object to include embedded reference points in the data structure. Whenever a Gamma object is printed, all circular references and self-references are detected before the object is printed, and reference points are inserted into the printed representation. Subsequent attempts to print an object that was previously printed will merely produce a reference to the first printing of the object. This facility produces a result that is essentially impossible in languages such as C; it perfectly preserves multiple pointer references to data which are not known, a priori, to be multiple references.
A very simple example of self-reference may be a list that contains itself. This is normally achieved using destructive functions such as nappend, rplaca and rplacd. Consider the following dialogue:
Gamma> a = list(1, 2, 3); (1 2 3) Gamma> rplacd (cdr (a), a); (1 2 (1 2 (1 2 (1 2 (1 2 (1 2 (1 2 (1 2 (1 2 ...)))))))))
In this case, by replacing the tail of the list with the list itself, it is possible to create a self-referential list which cannot be printed using normal means. Any attempt to print this list will cause an infinite loop. The Lisp writer in fact produces the following output:
Gamma> a = list(1, 2, 3); (1 2 3) Gamma> rplacd (cdr (a), a); #0=(1 2 . #0#)
The first time that the self-referential list is printed, the Gamma writer determines that a self- reference will occur, and marks that point with a numbered place holder, using the syntax #n=, where n is a monotonically increasing number counting the number of circular references in the data object. Each subsequent reference to the marked object will cause the writer to produce a reference back to the original using the syntax #n#. For example, if we create another, similar list, and then put both lists together into another list, we will get the following:
Gamma> b = list(4, 5, 6); (4 5 6) Gamma> rplacd (cdr (b), b); #0=(4 5 . #0#) Gamma> d = list(a,b); (#0=(1 2 . #0#) #1=(4 5 . #1#))
Using this method, arbitrarily complex objects can be written, with all circular and self-references maintained.
As a side effect of this printing mechanism, duplicate references to objects which are not circularly defined will also be caught and correctly reproduced. For example, suppose that a list contains a single string more than once. It would be wasteful to write that string many times, and would generate an incorrect result on reading if the multiple references to that string were not preserved. The Lisp writer will correctly handle this situation:
Gamma> x = "Hello"; "Hello" Gamma> a = list (x, x, x); (#0="Hello" #0# #0#) Gamma> eq (car(a), cadr (a)); t
In the above example, if the Gamma writer did not preserve the multiple references to the string "Hello", then a would be printed as:
("Hello" "Hello" "Hello")
When this object is read by the Gamma reader, we would get a list which is visibly the same but for which the data references no longer match:
Gamma> a = list("Hello", "Hello", "Hello"); ("Hello" "Hello" "Hello") Gamma> eq (car(a),cadr(a)); nil
Copyright © 1995-2010 by Cogent Real-Time Systems, Inc. All rights reserved.