Каковы ограничения читательских макросов в Common Lisp - PullRequest
1 голос
/ 25 мая 2019

У меня есть свой собственный интерпретатор Lisp в JavaScript, который я работаю уже некоторое время, и теперь я хочу реализовать макросы для чтения, как в Common Lisp.

Я создал потоки (почти работает, за исключением специальных символов)как ,@ , ` '), но он замораживает браузер на несколько секунд при загрузке страницы с включенными скриптами (файлы lisp, содержащие 400 строк кода).Это потому, что мои потоки основаны на функции подстроки.Если я сначала разделю токены, а затем использую TokenStream, который выполняет итерацию по токенам, он будет работать нормально.

Итак, мой вопрос заключается в следующем: действительно ли строковые потоки есть в Common Lisp?Можете ли вы добавить макросы для чтения, которые создают совершенно новый синтаксис, такой как Python, внутри CL, это упрощает вопрос: могу ли я реализовать макрос """ (не уверен, что вы можете использовать 3 символа в качестве макроса для чтения) или другой символ, который будет реализовывать литерал шаблона внутри lisp дляЭкземпляр:

(let ((foo 10) (bar 20))
  {lorem ipsum ${baz} and ${foo}})

или

(let ((foo 10) (bar 20))
  ""lorem ipsum ${baz} and ${foo}"")

или

(let ((foo 10) (bar 20))
  :"lorem ipsum ${baz} and ${foo}")

даст строку

"lorem ipsum 10 and 20"

что-то подобное в CommonLisp, а насколько сложно было бы реализовать #\{ или #\: в качестве макроса для чтения?

Единственный способ, которым я могу думать о наличии литералов шаблонов в Лиспе, это что-то вроде этого:

  (let ((foo 10) (bar 20))
    (tag "lorem ipsum ${baz} and ${foo}")))

где тег - это макрос, который возвращает строки с $ {} в качестве свободной переменной.Может ли макрос считывателя также возвращать код lisp, который был оценен?

И еще один вопрос: можете ли вы реализовать макросы считывателя следующим образом:

(list :foo:bar)


(list foo:bar)

, где: макрос считывателя и, если он находится перед символами, он преобразует символна

foo.bar

и, если он внутри, выдает ошибку.Я спрашиваю об этом, потому что в макросах на основе токенов :foo:bar и foo:bar будут символами и не будут обрабатываться моими макросами для чтения.

и еще один вопрос может быть вставлен в одну и вторую строку.линия использовать это?Это определенно будет возможно только для строковых потоков, а из того, что я тестировал, невозможно для интерпретатора, написанного на JavaScript.

1 Ответ

2 голосов
/ 26 мая 2019

Существуют некоторые ограничения в том смысле, что довольно сложно, например, вмешиваться в интерпретацию токенов, за исключением «реализации собственного интерпретатора токенов с нуля». Но, в общем, вы могли бы, если бы захотели сделать это: проблема в том, что ваш код должен был бы иметь дело с числами и вещами так же, как с существующим кодом, и такие вещи, как синтаксический анализ с плавающей точкой, довольно трудны для понимания. 1001 *

Но макрофункции, связанные с макро-символами, получают поток, который читается, и они могут свободно читать столько или меньше потока, сколько хотят, и возвращать любой тип объекта (или никакого объекта, который является как реализованы комментарии).

Я бы настоятельно рекомендовал прочитать главы 2 & 23 гиперспека, а затем поиграть с реализацией. Когда вы играете с реализацией, имейте в виду, что просто удивительно легко полностью заклинить вещи, обходя читателя. Как минимум, я бы предложил такой код:

(defparameter *my-readtable* (copy-readtable nil))

;;; Now muck around with *my-readtable*, *not* the default readtable
;;;

(defun experimentally-read ((&key (stream *standard-input*)
                                  (readtable *my-raedtable*)))
  (let ((*readtable* readtable))
    (read stream)))

Это дает вам хоть какой-то шанс оправиться от катастрофы: если вы однажды можете прервать experimentally-read, тогда вы вернетесь в положение, где *readtable* - это нечто разумное.

Вот довольно бесполезный пример, который показывает, насколько вы можете подорвать синтаксис с помощью макро-символов: определение макро-символа, которое приведет к тому, что ( ...) будет прочитан как строка. Это может быть не полностью отлажено, и, как я уже сказал, я не вижу в этом смысла.

(defun mindless-parenthesized-string-reader (stream open-paren)
  ;; Cause parenthesized groups to be read as strings:
  ;; - (a b) -> "a b"
  ;; - (a (b c) d) -> "a (b c) d"
  ;; - (a \) b) -> "a ) b"
  ;; This serves no useful purpose that I can see.  Escapes (with #\))
  ;; and nested parens are dealt with.
  ;;
  ;; Real Programmers would write this with LOOP, but that was too
  ;; hard for me.  This may well not be completely right.
  (declare (ignore open-paren))
  (labels ((collect-it (escaping depth accum)
             (let ((char (read-char stream t nil t)))
               (if escaping
                   (collect-it nil depth (cons char accum))
                 (case char
                   ((#\\)
                    (collect-it t depth accum))
                   ((#\()
                    (collect-it nil (1+ depth) (cons char accum)))
                   ((#\))
                    (if (zerop depth)
                        (coerce (nreverse accum) 'string)
                      (collect-it nil (1- depth) (cons char accum))))
                   (otherwise
                      (collect-it nil depth (cons char accum))))))))
    (collect-it nil 0 '())))

(defvar *my-readtable* (copy-readtable nil))

(set-macro-character #\( #'mindless-parenthesized-string-reader
                     nil *my-readtable*)

(defun test-my-rt (&optional (stream *standard-input*))
  (let ((*readtable* *my-readtable*))
    (read stream)))

А теперь

> (test-my-rt)
12
12

> (test-my-rt)
x
x

> (test-my-rt)
(a string (with some parens) and \) and the end)
"a string (with some parens) and ) and the end"
...