2

我在 JavaScript 中有自己的 Lisp 解释器,我已经工作了一段时间,现在我想实现像 Common Lisp 中的阅读器宏。

我已经创建了 Streams(除了特殊符号之外几乎可以工作,@ , ` '),但是当它加载包含脚本的页面(具有 400 行代码的lisp 文件)时,它会冻结浏览器几秒钟。这是因为我的 Streams 是基于 substring 函数的。如果我首先拆分令牌,然后使用迭代令牌的 TokenStream,它工作正常。

所以我的问题是,字符串流真的是 Common Lisp 中的东西吗?您能否在 CL 中添加创建全新语法(如 Python)的阅读器宏,这简化了我是否可以实现"""宏(不确定是否可以将 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"

在 Common Lisp 中是否可能发生这样的事情,实现#\{#\:作为阅读器宏有多难?

我能想到的在 Lisp 中使用模板文字的唯一方法是这样的:

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

其中 tag 是返回带有 ${} 作为自由变量的字符串的宏。阅读器宏也可以返回被评估的 lisp 代码吗?

还有一个问题,你可以像这样实现阅读器宏:

(list :foo:bar)


(list foo:bar)

其中 : 是阅读器宏,如果它在符号之前,它将符号转换为

foo.bar

如果它在里面,它会抛出错误。我问这个是因为使用基于令牌的宏:foo:bar并且foo:bar将是符号并且不会被我的阅读器宏处理。

还有一个问题可以将阅读器宏放在一行中,第二行使用它吗?这肯定只有字符串流才有可能,而我测试过的解释器用 JavaScript 编写的解释器是不可能的。

4

1 回答 1

5

存在一些限制,例如,除了“从头开始实现自己的令牌解释器”之外,很难以任何方式干预令牌的解释。但是,如果您想这样做,您可以这样做:问题是您的代码需要像现有代码一样处理数字和事物,并且浮点解析之类的事情非常繁琐。

但是与宏字符相关联的宏函数获取正在读取的流,并且它们可以自由地读取尽可能多或尽可能少的流,并返回任何类型的对象(或不返回对象,这就是注释的方式)实施的)。

我强烈建议阅读hyperspec 的第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"
于 2019-05-26T10:54:42.633 回答