Общий синтаксический анализатор Lisp, который сохраняет комментарии

100
11

Common Lisp поставляется с синтаксическим анализатором (читателем), который преобразует его текстовый синтаксис в s-выражения. Тем не менее, он отбрасывает комментарии, что делает его непригодным для инструментов, которые используют код Lisp в оба конца.

Есть ли существующий парсер для Common Lisp, который сохраняет комментарии?

спросил(а) 2021-01-19T15:56:43+03:00 6 месяцев, 1 неделя назад
1
Решение
134

Как отмечено в комментарии, вы должны иметь возможность модифицировать readtable для привязки символа макроса ; к другой функции макроса читателя. Например, если вы определяете:

(defun semicolon-reader (stream char)
(list 'my-comment
(concatenate 'string (string char)
(read-line stream nil #\Newline t))))

а затем при запуске верхнего уровня:

> (set-macro-character #\; #'semicolon-reader)
> (read)

пользовательский ввод:


(a b ; is b
c ; is c
)

будет генерировать:

(A B (MY-COMMENT "; is b") C (MY-COMMENT "; is c"))

Тем не менее, реальная обработка в оба конца также потребует сохранения пробелов. Я не знаю достаточно, чтобы читатель узнал, можете ли вы уйти с определением некоторых умных макрофункций для пробельных символов, или если вам придется написать какую-то функцию предварительной обработки, чтобы предварительно процитировать пробеги пробела с другим символом макроса а затем обрабатывать его аналогично semicolon-reader выше.

ответил(а) 2021-01-19T15:56:43+03:00 6 месяцев, 1 неделя назад
77

Поскольку вы создаете форматировщик кода, вы можете уйти с поддержкой только стандартного синтаксиса Common Lisp (with-standard-io-syntax) и изменения стандартного readtable для сохранения комментариев и пробелов (set-macro-character). Чтение является основной структурой данных CL-считывателя и сообщает, какую функцию вызывать для чтения различных объектов, когда он встречает определенный символ в исходном коде (например, как читать список, когда он встречает открывающую скобку).

Вы должны использовать либо gensyms, либо structs/classes для представления комментариев и пробелов, поскольку другие типы объектов (например, списки и не-gensym символы) могут быть прочитаны из исходного файла с помощью читателя Lisp с использованием стандартного синтаксиса IO.

Ниже приведено быстрое доказательство концепции. Читатель прекрасно работает, но я не мог заставить принтер работать (то есть перепечатать материал, который мы получили от читателя, чтобы создать исходный файл, достаточно близко к вводу) - он печатает лишние пробелы вокруг наших пробелов, возможно, потому, что он думает наши пробельные объекты похожи на обычные объекты Lisp (символы, списки и т.д.) и должны быть разделены пробелами при печати нескольких из них в строке (например, если вы печатаете 1 и 2 и 3 она должна печатать 1 2 3 не 123), Дайвинг в кишках принтера Common Lisp, чтобы выяснить, как переопределить это поведение, остается как упражнение для читателя: p

Кроме того, прочтите раздел 2.4 "Стандартные макросимволы общего Lisp HyperSpec". Раздел 2.4.8 Sharpsign перечисляет весь синтаксис, начинающийся с #. Остерегайтесь особенно #+ и #- и #.

Если вы когда-нибудь получите это, чтобы хорошо работать на реальном коде, рассмотрите его публикацию как пакет с открытым исходным кодом.

(defstruct comment style string)
(defstruct whitespace string)

(defconstant +whitespace-chars+ '(#\Space #\Tab #\Return #\Newline))
(defconstant +eof+ (gensym "EOF"))

(defun read-semicolon-comment (stream semicolon)
(declare (ignore semicolon))
(make-comment :style :semicolon :string
(with-output-to-string (comment)
(loop (let ((char (read-char stream nil +eof+ t)))
(cond ((equal char +eof+) (return))
((equal char #\Newline)
(unread-char char stream)
(return))
(t (write-char char comment))))))))

(defun read-whitespace (stream first-char)
(make-whitespace :string
(with-output-to-string (whitespace)
(write-char first-char whitespace)
(loop (let ((char (read-char stream nil +eof+ t)))
(unless (member char +whitespace-chars+)
(unless (equal char +eof+) (unread-char char stream))
(return))
(write-char char whitespace))))))

(defun read-stream (stream)
(with-standard-io-syntax ; Here a comment, for example.
(let ((*readtable* (copy-readtable)))
(set-macro-character #\; #'read-semicolon-comment)
(dolist (char +whitespace-chars+)
(set-macro-character char #'read-whitespace))
(loop for x = (read stream nil +eof+) until (equal x +eof+)
collect x))))

(defmethod print-object ((x comment) stream)
(assert (equal :semicolon (comment-style x)))
(write-char #\; stream)
(write-string (comment-string x) stream)
x)

(defmethod print-object ((x whitespace) stream)
(write-string (whitespace-string x) stream)
x)

(mapc #'prin1 (read-stream *standard-input*))

ответил(а) 2021-01-19T15:56:43+03:00 6 месяцев, 1 неделя назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема