#common-lisp
#common-lisp
Вопрос:
Я хочу определить что-то, что принимает в качестве аргумента «строку», не заключенную в двойные кавычки. Например:
(f The quick brown fox.) ; Returns: "The quick brown fox."
(f The quick brown fox. ) ; Returns: " The quick brown fox. "
(f [:case 'upper] The quick brown fox.) ; Returns: "THE QUICK BROWN FOX."
(f [:caps 'lower] The quick brown fox. ) ; Returns: " the quick brown fox. "
(f [:caps 'lower :trim t] The quick brown fox. ) ; Returns: "the quick brown fox."
Похоже, что макросы не подходят для этой задачи. Действительно ли это возможно в Common Lisp?
Если это возможно, не могли бы вы рассказать мне, какие функции Common Lisp мне нужно будет изучить / использовать для достижения вышеуказанного? Мне не нужно давать решение; Мне просто нужно, чтобы кто-то указал мне правильное направление.
Если это невозможно, не могли бы вы объяснить, почему?
Комментарии:
1. @adabsurdum Мне просто интересно, обладает ли Common Lisp такой гибкостью. Аргумент «string» (все варианты
The quick brown fox.
) всегда будет буквально там (т. Е. Явно Указан в качестве аргумента).2. @adabsurdum Да,
f
это только для удобства ввода. Его аргументы не будут сгенерированы программно.3. В каждом языке программирования строки должны быть разделены каким-либо образом, чтобы анализатор мог их правильно прочитать. В противном случае, если у вас есть функция, которая принимает две строки, как вы разделяете ее аргументы? Например, если
g
это функция, которая принимает две строки, как вы интерпретируете(g these are two strings)
?
Ответ №1:
Вы можете взломать программу чтения Lisp вплоть до того момента, когда будет возможен синтаксис, который вы хотите использовать. Точнее, вы можете взломать макрос считывателя, связанный с открытой скобкой, чтобы определить, начинается ли форма с (f ...
, и в этом случае вы анализируете входной поток по своему усмотрению, или же вы откладываете чтение до существующего считывателя.
Однако это хак, который может быть нелегко совместить с другими настройками таблицы чтения. Лучшим решением было бы определить пользовательский макрос reader SET-MACRO-CHARACTER
, скажем §(...)
, который считывает строку до закрывающей круглой скобки.
§(The quick brown fox.)
Или:
§[:caps 'lower :trim t](The quick brown fox.)
Приведенное выше было бы эквивалентно форме, подобной этой, где str
находится ваш собственный макрос или функция:
(str "The quick brown fox." :caps 'lower :trim t)
Но вы должны быть осторожны с угловыми случаями.
Например, если вы хотите использовать круглые скобки в своей строке:
§(The quick (brown) fox.)
приведенное выше будет читаться как:
"The quick (brown"
Поскольку закрывающие круглые скобки используются для остановки чтения строки, она не может отображаться в самой строке. Если это неприемлемо, вам нужно расширить синтаксис.
Например, вам нужно определить, как экранировать символы. Обычно это означает префикс символов с обратной косой #
чертой.
Также одним из возможных преимуществ этого синтаксиса является возможность вставлять строки без необходимости экранировать двойные кавычки (точно так же, как в bash, это $()
лучший синтаксис, чем обратные ссылки):
§(The quick §(brown) fox.)
В идеале это было бы так же, как:
"The quick "brown" fox."
Это означает, что now #§
также является специальным символом в вашем собственном мини-языке.
Это может сработать и может быть интересно реализовать, но оно того стоит? Уже существует краткий синтаксис для записи строк с двойными кавычками.
f используется только для удобства ввода.
Я имею в виду, что для записи требуется больше символов (f ...)
, Чем "..."
, а преобразование регистра и т. Д. Можно выполнить с помощью функций или макросов с короткими именами, если хотите. Может быть, вы могли бы начать с определения str
формы и посмотреть, достаточно ли этого.
Ответ №2:
Как говорит coredump, правильный подход к этому — это своего рода макрос чтения: вы, очевидно, можете делать с reader все, что хотите, но обычно вы хотите делать вещи, которые не слишком сильно портят все остальное.
Единственный полезный случай, который я вижу для чего-то подобного (и я все больше не одобряю «Я хочу сделать эту безумную вещь только потому, что я могу» идеи: изменения синтаксиса, особенно изменения синтаксиса чтения, должны быть полезными), — это иметь некоторое представление о считывателе строк, которое позволяет вам указыватьразличные операции над прочитанной строкой, такие как ее обрезка. Я, конечно, не понимаю, почему кто-то хотел бы вводить (s ...)
вместо "..."
, а не иначе. Я также не собираюсь пытаться использовать все, что читает левша, потому что это определенно затруднит выполнение критерия «не слишком запутывайте все остальное«. И, наконец, поскольку я слишком ленив, чтобы писать средство чтения строк с разделителями (это не сложно, но может быть немного неудобно разобраться), ниже будут фактически считываться строки, разделенные `#»…»: таким образом, я могу просто перейти к существующему средству чтения строк для большей частиработа.
Прежде всего предположим, что функция process-string
, задача которой состоит в том, чтобы массировать некоторую строку на основе аргументов ключевого слова. Вот версия, которая просто позволяет обрезать:
(defun process-string (s amp;key (trim nil))
(let ((ss s))
(when trim
(setf ss (string-trim '(#Space #Tab #Newline) ss)))
ss))
Теперь мы не будем разбивать стандартную таблицу чтения:
(defvar *silly-rt* (copy-readtable nil))
И мы собираемся определить средство чтения, которое находится на #"
(которое не используется в стандартной таблице чтения), которое будет считывать строки с некоторыми параметрами, чтобы что-то с ними делать.
Но обратите внимание, что, поскольку время чтения должным образом опережает время оценки, если мы хотим иметь возможность управлять вещами во время оценки, тогда этот макрос чтения должен превратиться не в строку, а в форму, включающую process-string
, так что это то, что он будет делать.
#"xyz"
будет читаться как(process-string "xyz")
;#"(:trim t) xyz "
будет читаться как(process-string " xyz " :trim t)
;#"(:trim t) xyz"
будет читаться как(process-string "(:trim t) xyz")
(вы можете экранировать open paren, чтобы удалить его);#" (:trim t) xyz"
будет читаться как(process-string " (:trim t) xyz")
(открытый paren должен быть самым первым символом).
Вот функция, подходящая для присоединения к символу макроса диспетчерского чтения, который выполняет это:
(defun read-silly-string (stream char prefix)
(declare (ignore char prefix))
(let ((controls (if (char= (peek-char nil stream t) #()
(read stream t nil t)
'())))
`(process-string ',(funcall (get-macro-character #")
stream #")
,@controls)))
Примечания.
- Он просто просматривает первый символ, чтобы узнать, есть ли там какие-либо элементы управления.
- Он узнает, как читать строки, вызывая любое обычное средство чтения строк, просто просматривая его в таблице чтения.
Теперь мы можем вставить это в таблицу чтения и определить тестовую функцию:
(set-dispatch-macro-character
## #"
#'read-silly-string
*silly-rt*)
(defun silly-read ()
(let ((*readtable* *silly-rt*))
(read)))
И теперь:
> (silly-read)
#""
(process-string '"")
> (silly-read)
#"(:trim t) x y"
(process-string '" x y" :trim t)
> (silly-read)
(let ((trim nil))
#"(:trim trim) x y")
(let ((trim nil)) (process-string '" x y" :trim trim))
> (silly-read)
(let ((trim nil))
#" (:trim trim) x y")
(let ((trim nil)) (process-string '" (:trim trim) x y"))