6

我正在尝试以下列方式定义符号 a 和 b

a + 1 1 b
2

我正在尝试通过使用 define-symbol-macro 来做到这一点

(define-symbol-macro a '( )
(define-symbol-macro b ') )

但这种方式行不通。

4

3 回答 3

24

Lisp 对源代码的作用

Common Lisp 是一种非常灵活的语言,部分原因是它的源代码可以很容易地使用该语言中使用的相同数据结构来表示。最常见的宏观扩展形式将这些结构转换为其他结构。这些是您可以使用define-symbol-macrodefine-compiler-macrodefmacro和定义的宏macrolet。然而,在执行任何此类宏扩展之前,系统首先需要从输入流(通常是文件或交互式提示)中读取源代码。那是读者的责任。阅读器还可以在遇到某些字符时执行一些特殊操作,(例如'. (read-from-string "a + 1 1 b")如果您想拥有例如return list ,那么您尝试做的事情可能需要在读者级别发生,如果您想return (+ 1 1),这就是您想要的。也就是说,您还可以定义一种特殊的自定义语言(就像那样),在哪里和被特殊对待。(eval (read-from-string "a + 1 1 b"))2loopab

使用set-macro-character,而不是define-symbol-macro

这不是您可以使用符号宏执行的操作,而是使用宏字符执行的操作。您可以使用恰当命名的set-macro-character. 例如,在下面,我将宏字符 for 设置为%读取列表的函数,使用read-delimited-list它应该以 . 结尾^。(在这里使用字符ab会非常困难,因为你不能像(set-macro-character ...)之后那样写东西;这就像写(set-m(cro-ch(r(cter ...),这不好。)

CL-USER> (set-macro-character #\% (lambda (stream ignore)
                                    (declare (ignore ignore))
                                    (read-delimited-list #\^ stream)))
T
CL-USER> % + 1 1 ^
2

相关的set-syntax-from-char

这里有一个相关的功能几乎可以满足您的需求,set-syntax-from-char. 您可以使用它来使一个角色表现得像另一个角色。例如,你可以让%行为像(

CL-USER> (set-syntax-from-char #\% #\()
T
CL-USER> % + 1 1 )
2

但是,由于与 关联的宏字符(不是在寻找与 具有相同语法的字符),而是一个实际 )字符,因此您不能以相同的方式简单地替换)^

CL-USER> (set-syntax-from-char #\^ #\))
T
CL-USER> % + 1 1 ^
; Evaluation aborted on #<SB-INT:SIMPLE-READER-ERROR "unmatched close parenthesis" {1002C66031}>.

set-syntax-from-char当存在一个现有的角色,它本身会做一些你想模仿的事情时,它会更有用。例如,如果您想添加!一个额外的引号字符:

CL-USER> (set-syntax-from-char #\! #\')
T
CL-USER> (list !a !(1 2 3))
(A (1 2 3))

或者做%一个评论角色,就像在 LaTeX 中一样:

CL-USER> (set-syntax-from-char #\% #\;)
T
CL-USER> (list 1 2 % 3 4
               5 6)
(1 2 5 6)

但是请考虑一下您为什么要这样做……</h3>

现在,即使您可以做到所有这些,但对于遇到它的任何人来说,这似乎都是完全令人惊讶的事情。(也许您正在参加一场混淆编码比赛?;))由于上述原因,使用常用字符(例如a和)这样做b也会使编写更多源代码变得非常困难。定义一个完全新的 readtable 来做你想做的事情,甚至编写一个新的解析器可能是一个更好的选择。尽管 (Common) Lisp可以让你重新定义语言,但仍有一些事情可能是有意义的。

于 2013-10-11T15:02:23.673 回答
3

符号宏是代表另一种形式的符号。似乎您想查看阅读器宏。

不过,我会同意 Rainer 的评论,你想做什么?

于 2013-10-11T13:33:29.887 回答
2

好的,所以我喜欢您对此原因的评论,现在我知道这是因为“只是因为它是 lisp”,所以我完全同意!

好的,所以你说 lisp 非常适合用来制作新语言是对的,因为我们只需要“编译”成有效的 lisp 代码,它就会运行。因此,虽然我们不能使用普通编译器将符号 'a 和 'b 转换为括号,但我们可以自己编写。

好的,让我们开始吧!

(defun symbol-name-equal (a b)
  (and (symbolp a) (symbolp b) (equal (symbol-name a) (symbol-name b))))

(defun find-matching-weird (start-pos open-symbol close-symbol code)
  (unless (symbol-name-equal open-symbol (nth start-pos code))
    (error "start-pos does not point to a weird open-symbol"))
  (let ((nest-index 0))
    (loop :for item :in (nthcdr start-pos code) 
       :for i :from start-pos :do
       (cond ((symbol-name-equal item open-symbol) (incf nest-index 1))
             ((symbol-name-equal item close-symbol) (incf nest-index -1)))
       (when (eql nest-index 0)
         (return i))
       :finally (return nil))))

(defun weird-forms (open-symbol close-symbol body)
  (cond ((null body) nil)
        ((listp body) 
         (let ((open-pos (position open-symbol body :test #'symbol-name-equal)))
           (if open-pos
               (let ((close-pos (find-matching-weird open-pos open-symbol close-symbol body)))
                 (if close-pos
                     (weird-forms open-symbol close-symbol
                                  `(,@(subseq body 0 open-pos) 
                                      (,@(subseq body (1+ open-pos) close-pos))
                                      ,@(subseq body (1+ close-pos))))
                     (error "unmatched weird brackets")))
               (if (find close-symbol body :test #'symbol-name-equal)
                   (error "unmatched weird brackets")
                   (loop for item in body collect 
                        (weird-forms open-symbol close-symbol item))))))
        (t body)))


(defmacro with-weird-forms ((open-symbol close-symbol) &body body)
  `(progn
     ,@(weird-forms open-symbol close-symbol body)))

所以这有几个部分。

首先我们有 (symbol-name-equal),这是一个辅助函数,因为我们现在使用的符号和符号属于包。symbol-name-equal 为我们提供了一种检查符号是否具有相同名称的方法,而忽略它们所在的包。

其次我们有(发现匹配奇怪)。这是一个函数,它将列表和索引指向一个奇怪的左括号,并将索引返回到一个奇怪的右括号。这确保我们得到正确的括号,即使有嵌套

接下来我们有(奇怪的形式)。这是有趣的一点,它的作用是递归遍历作为“body”参数传递的列表并执行以下操作:

  • 如果 body 是一个空列表,只需返回它
  • 如果 body 是一个列表,那么
    • 找到我们的打开和关闭符号的位置。
    • 如果只找到其中​​一个,那么我们有不匹配的括号。
    • 如果我们找到两个符号,则创建一个新列表,其中包含嵌套列表中开始位置和结束位置之间的位。
    • 然后我们在这个结果上调用奇怪的形式,以防里面有更多奇怪的符号形式。
  • 没有奇怪的符号,然后只需遍历列表中的项目并在它们上调用奇怪的形式以继续搜索。

好的,这样函数就会转换一个列表。例如尝试:

(weird-forms 'a 'b '(1 2 3 a 4 5 b 6 7))

但是我们希望这是可以执行的正确 lisp 代码,因此我们需要使用一个简单的宏。(with-weird-forms) 是一个宏,它调用了奇怪的形式函数并将结果放入我们的源代码中以由 lisp 编译。所以如果我们有这个:

(with-weird-forms (a b)
  (+ 1 2 3 a - a + 1 2 3 b 10 5 b 11 23))

然后它宏扩展为:

(PROGN (+ 1 2 3 (- (+ 1 2 3) 10 5) 11 23))

这是完全有效的 lisp 代码,所以它会运行!

CL-USER> (with-weird-forms (a b)
           (+ 1 2 3 a - a + 1 2 3 b 10 5 b 11 23))
31

最后,如果您已经确定了“a”和“b”括号,您可以编写另一个小宏:

(defmacro ab-lang (&rest code)
  `(with-weird-forms (a b) ,@code))

现在试试这个:

(ab-lang a let* a a d 1 b a e a * d 5 b b b a format t "this stupid test gives: ~a" e b b)

干杯伙伴,这写起来很有趣。很抱歉早些时候解决了这个问题。

这种编码非常重要,因为最终这是一个用于我们奇怪语言的小型编译器,其中符号可以是标点符号。编译器很棒,没有任何语言可以像 lisp 那样轻松编写它们。

和平!

于 2013-10-20T10:42:13.473 回答