jcu*_*bic 2 lisp macros common-lisp reader-macro
我在 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}})
Run Code Online (Sandbox Code Playgroud)
或者
(let ((foo 10) (bar 20))
""lorem ipsum ${baz} and ${foo}"")
Run Code Online (Sandbox Code Playgroud)
或者
(let ((foo 10) (bar 20))
:"lorem ipsum ${baz} and ${foo}")
Run Code Online (Sandbox Code Playgroud)
会产生字符串
"lorem ipsum 10 and 20"
Run Code Online (Sandbox Code Playgroud)
在 Common Lisp 中,这样的事情是可能的,实现#\{或#\:作为读者宏会有多难?
我能想到的在 Lisp 中使用模板文字的唯一方法是这样的:
(let ((foo 10) (bar 20))
(tag "lorem ipsum ${baz} and ${foo}")))
Run Code Online (Sandbox Code Playgroud)
其中 tag 是返回带有 ${} 作为自由变量的字符串的宏。阅读器宏也可以返回被评估的 lisp 代码吗?
还有一个问题,你能不能实现这样的阅读器宏:
(list :foo:bar)
(list foo:bar)
Run Code Online (Sandbox Code Playgroud)
其中:是阅读器宏,如果它在符号之前,则将符号转换为
foo.bar
Run Code Online (Sandbox Code Playgroud)
如果它在里面,它会抛出错误。我问这个是因为使用基于令牌的宏:foo:bar并且foo:bar将是符号并且不会被我的阅读器宏处理。
还有一个问题,reader宏可以放在一行第二行使用吗?这肯定只能通过字符串流和我测试过的用 JavaScript 编写的解释器不可能实现。
小智 5
从某种意义上说,存在一些限制,例如,除了“从头开始实现自己的令牌解释器”之外,很难以任何方式干预令牌的解释。但是,好吧,如果您只想这样做,您可以:问题是您的代码需要像现有代码一样处理数字和事物,而浮点解析之类的东西要正确处理非常繁琐。
但是与宏字符关联的宏函数获取正在读取的流,它们可以自由地读取任意数量的流,并返回任何类型的对象(或没有对象,这就是注释的方式)实施的)。
我会强烈建议阅读的章节2和23的hyperspec的,然后用一个实现播放。当您使用该实现时,请注意通过与读者混在一起来完全楔入事物是非常容易的。至少我会建议这样的代码:
(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)))
Run Code Online (Sandbox Code Playgroud)
这至少给了你一些从灾难中恢复过来的机会:如果你能一次中止,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)))
Run Code Online (Sandbox Code Playgroud)
现在
> (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"
Run Code Online (Sandbox Code Playgroud)