First step: use conventional formatting. See e. g. Practical Common Lisp.
(defun print-list (elements)
(cond
((null elements) (princ ") "))
(t
(cond ((listp (car elements))
;; (princ #\Space)
(princ "( ")
(print-list (car elements))
(format t "~%")))
(if (atom (car elements))
(prin1 (car elements)))
(format t "~%")
(princ #\Space)
(print-list (cdr elements)))))
The task said to name it pretty-print
, and that isn't taken by any standard function, so let's just do that:
(defun pretty-print (elements)
(cond
((null elements) (princ ") "))
(t
(cond ((listp (car elements))
(princ "( ")
(pretty-print (car elements))
(format t "~%")))
(if (atom (car elements))
(prin1 (car elements)))
(format t "~%")
(princ #\Space)
(pretty-print (cdr elements)))))
If we try (pretty-print '(a (b c de) fg))
, we get:
A
( B
C
DE
)
FG
)
At least all the symbols are there in the right order, and there seems to be only one parenthesis missing. The opening parenthesis seems to be only printed in some cases, but missing at the very start.
In order to proceed, I'd like to clean up a little. Having nested cond
s and if
s is in most cases not necessary; you can combine them into a single cond
. For that, we note that (listp (car elements))
and (atom (car elements))
seem to be intended to be mutually exclusive. Since the null
check should do an early exit, we'll do that with an explicit return in order to eliminate that nesting as well. We get the three cases then:
(defun pretty-print (elements)
(cond ((null elements)
(princ ") ")
(return-from pretty-print))
((listp (car elements))
(princ "( ")
(pretty-print (car elements))
(format t "~%"))
((atom (car elements))
(prin1 (car elements))))
(format t "~%")
(princ #\Space)
(pretty-print (cdr elements)))
Now, for that opening parenthesis, I'd try a simpler test case: the empty list.
(pretty-print '())
which prints
)
That's not right. But that's exactly what the first cond
branch says. However, if we do a (princ "()")
here, there will be an additional opening parenthesis at the end of every non-empty list.
We conflated two things here: the printing of the empty list, and finishing with printing a list. You can't discern between these if you just do a simple recursion, because every tail of a list is itself a list and the last one empty.
I'd do the conditions on the input itself. Now you can say something like “in order to print a list, print an opening parenthesis, then print the contents, then a closing parenthesis”.
(defun pretty-print (elements)
(cond ((null elements)
(princ "()")
(return-from pretty-print))
((listp elements)
(princ "(")
(pretty-print-elements elements)
(princ ")"))
((atom elements)
(prin1 elements)))
(format t "~%")
(princ #\Space))
I moved the recursion into the list into a different function (not shown yet). Now there is an explicit match-up of the parentheses, and it cannot happen that one is printed but the other not. Here is pretty-print-elements
in a recursive style (so that we do not have to introduce more advanced operators):
(defun pretty-print-elements (elements)
(unless (null elements)
(pretty-print (first elements))
(pretty-print-elements (rest elements))))
Let's try these:
(pretty-print '())
()
(pretty-print '(a))
(A
)
(pretty-print '(a (b c de) fg))
(A
(B
C
DE
)
FG
)
That's not very pretty yet, but at least everything matches up. There are two problems left: the closing parentheses should not be on a new line, and the elements should be aligned.
First, I move the separators between elements into the other function—it's actually part of printing the list, not of printing the element.
(defun pretty-print (elements)
(cond ((null elements)
(princ "()")
(return-from pretty-print))
((listp elements)
(princ "(")
(pretty-print-elements elements)
(princ ")"))
((atom elements)
(prin1 elements))))
(defun pretty-print-elements (elements)
(unless (null elements)
(pretty-print (first elements))
(terpri)
(princ #\Space)
(pretty-print-elements (rest elements))))
I also introduced terpri
here, which prints a newline.
Now it's quite obvious to omit that separation after the last element:
(defun pretty-print-elements (elements)
(unless (null elements)
(pretty-print (first elements))
(unless (null (rest elements))
(terpri)
(princ #\Space))
(pretty-print-elements (rest elements))))
Again, there are more concise ways to express this, but let's keep it on a basic language level. Try it:
(pretty-print '(a (b c de) fg))
(A
(B
C
DE)
FG)
Finally, the indentation. Currently, everything gets indented one space. However, we want to indent it more the deeper we are in the recursion, so we need to keep track of the level.
(defun pretty-print (elements indentation)
(cond ((null elements)
(princ "()")
(return-from pretty-print))
((listp elements)
(princ "(")
(pretty-print-elements elements (1+ indentation))
(princ ")"))
((atom elements)
(prin1 elements))))
(defun pretty-print-elements (elements indentation)
(unless (null elements)
(pretty-print (first elements) indentation)
(unless (null (rest elements))
(terpri)
(print-indentation indentation))
(pretty-print-elements (rest elements) indentation)))
Try it:
(pretty-print '(a (b c de) fg) 0)
(A
(B
C
DE)
FG)
Okay. This is how you could format S-Expressions. The extra whitespace shown in your question is most likely a misinterpretation from some printout.
The implementation of print-indentation
, as well as making it optional to specify the initial indentation are left as exercises for the reader. You can also make each of these functions more concise by taking a look at what is actually redundant.