Common Lisp中的读取器宏有哪些限制

时间:2019-05-25 15:16:07

标签: macros lisp common-lisp reader-macro

我有一段时间在JavaScript中使用自己的Lisp解释器,现在我想像在Common Lisp中那样实现阅读器宏。

我已经创建了Streams(除了,@ , ` '之类的特殊符号以外,几乎可以正常工作),但是当它用包含的脚本(具有400行代码的lisp文件)加载页面时,它将冻结浏览器几秒钟。这是因为我的流基于子字符串函数。如果我先拆分令牌,然后使用遍历令牌的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}")))

其中标记是宏,该宏返回以$ {}作为自由变量的字符串。阅读器宏还可以返回经过评估的Lisp代码吗?

还有另一个问题,您可以实现这样的阅读器宏吗:

(list :foo:bar)


(list foo:bar)

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

foo.bar

,如果在其中,则抛出错误。我之所以这样问,是因为基于令牌的宏:foo:barfoo:bar将是符号,不会被我的阅读器宏处理。

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

1 个答案:

答案 0 :(得分:2)

从某种意义上说,存在一定的局限性,例如,除了“从头实现自己的令牌解释器”之外,很难以任何方式干预令牌的解释。但是,好吧,如果您只想这样做:问题是您的代码将需要像现有代码一样处理数字和事物,并且浮点解析之类的事情很容易就可以解决。 / p>

但是与宏字符关联的宏函数获取正在读取的流,并且它们可以自由读取任意多或少的流,并返回任何类型的对象(或不返回任何对象,即评论的实现方式。)

我强烈建议您阅读hyperspec的第223章,然后尝试实现。当您使用实现时,请注意,通过与读者混为一谈,完全楔入东西是非常容易的。至少我会建议这样的代码:

var xPositions = Xaxis.getVerticesData(BABYLON.VertexBuffer.PositionKind);
        xPositions[1] = xPositions[4] =  (vy/100)*newVal, xPositions[2] = xPositions[5] = (vz/100)*newVal;
        Xaxis.updateVerticesData(BABYLON.VertexBuffer.PositionKind, xPositions);

这至少使您有机会从灾难中恢复过来:如果您一次可以放弃(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)))