Первый шаг: использовать обычное форматирование.См., Например, 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)))))
Задача, названная по имени pretty-print
, и это не выполняется ни одной стандартной функцией, поэтому давайте просто сделаем это:
(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)))))
Если мы попробуем (pretty-print '(a (b c de) fg))
, мы получим:
A
( B
C
DE
)
FG
)
По крайней мере, все символы находятся в правильном порядке, и, кажется, отсутствует только одна скобка.Открывающая скобка, кажется, печатается только в некоторых случаях, но отсутствует в самом начале.
Чтобы продолжить, я бы хотел немного привести себя в порядок.Вложение cond
s и if
s в большинстве случаев не требуется;Вы можете объединить их в один cond
.Для этого отметим, что (listp (car elements))
и (atom (car elements))
кажутся взаимоисключающими.Поскольку проверка null
должна выполнять ранний выход, мы сделаем это с явным возвратом, чтобы также устранить это вложение.Тогда мы получим три случая:
(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)))
Теперь, для этой открывающей скобки, я бы попробовал более простой тестовый случай: пустой список.
(pretty-print '())
, который печатает
)
Это не правильно.Но это именно то, что говорит первая ветка cond
.Однако, если мы сделаем (princ "()")
здесь, в конце каждого непустого списка будет добавлена открывающая скобка.
Здесь мы объединяем две вещи: печать пустого списка и завершениепечать списка.Вы не можете различить их, если вы просто делаете простую рекурсию, потому что каждый хвост списка сам по себе является списком, а последний пустой.
Я бы выполнил условия для самого ввода.Теперь вы можете сказать что-то вроде «чтобы напечатать список, напечатайте открывающую скобку, затем напечатайте содержимое, а затем закрывающую скобку».
(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))
Я переместил рекурсию в список в другую функцию(пока не показано).Теперь есть явное совпадение скобок, и не может быть так, что один напечатан, а другой нет.Вот pretty-print-elements
в рекурсивном стиле (так что нам не нужно вводить более продвинутые операторы):
(defun pretty-print-elements (elements)
(unless (null elements)
(pretty-print (first elements))
(pretty-print-elements (rest elements))))
Давайте попробуем это:
(pretty-print '())
()
(pretty-print '(a))
(A
)
(pretty-print '(a (b c de) fg))
(A
(B
C
DE
)
FG
)
Это пока не очень красиво, но, по крайней мере, все совпадает.Осталось две проблемы: закрывающие скобки не должны находиться на новой строке, а элементы должны быть выровнены.
Сначала я перемещаю разделители между элементами в другую функцию - это фактически часть печати спискане печатать элемент.
(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))))
Я также ввел здесь terpri
, который печатает новую строку.
Теперь совершенно очевидно, что пропустить это разделение после последнего элемента:
(defun pretty-print-elements (elements)
(unless (null elements)
(pretty-print (first elements))
(unless (null (rest elements))
(terpri)
(princ #\Space))
(pretty-print-elements (rest elements))))
Опять же, есть более краткие способы выразить это, но давайте оставим это на базовом уровне языка.Попробуйте это:
(pretty-print '(a (b c de) fg))
(A
(B
C
DE)
FG)
Наконец, отступ.В настоящее время все отступает на один пробел.Тем не менее, мы хотим сделать отступ больше, чем глубже мы находимся в рекурсии, поэтому нам нужно отслеживать уровень.
(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)))
Попробуйте:
(pretty-print '(a (b c de) fg) 0)
(A
(B
C
DE)
FG)
Хорошо.Вот как вы можете отформатировать S-Expressions.Дополнительный пробел, показанный в вашем вопросе, скорее всего, неверно истолкован из некоторой распечатки.
Реализация print-indentation
, а также необязательное указание начального отступа оставлены в качестве упражнений для читателя.Вы также можете сделать каждую из этих функций более краткой, взглянув на то, что на самом деле является избыточным.