Syntax Common Lisp:如何创建连接前缀宏,类似于';?

Syntax Common Lisp:如何创建连接前缀宏,类似于';?,syntax,common-lisp,notation,prefix-operator,Syntax,Common Lisp,Notation,Prefix Operator,通用Lisp宏通常使用包含的前缀表示法:(运算符填充…) 但是,特殊quote宏“使用串联前缀表示法:运算符stuff,或者运算符(stuff) 我想在SBCL Common Lisp中创建一个自定义宏,我们称之为!,它使用连接前缀语法对以下列表(甚至原子)进行操作,方式与“”类似。因此,我可以在任何地方调用它,例如(setqfoo!(bar)),而不必将它用中缀括在括号中 如何做到这一点?defmacro语法是什么样子的?谢谢。我有两种方法 第一个简单的变体是合适的,如果您只需要一个或数量非常

通用Lisp宏通常使用包含的前缀表示法:(运算符填充…)

但是,特殊quote宏“使用串联前缀表示法:运算符stuff,或者运算符(stuff)

我想在SBCL Common Lisp中创建一个自定义宏,我们称之为!,它使用连接前缀语法对以下列表(甚至原子)进行操作,方式与“”类似。因此,我可以在任何地方调用它,例如(setqfoo!(bar)),而不必将它用中缀括在括号中

如何做到这一点?defmacro语法是什么样子的?谢谢。

我有两种方法

第一个简单的变体是合适的,如果您只需要一个或数量非常有限的案例,并且只使用一个符号作为前缀(如示例案例中的
)。您可以创建一个读取宏,更具体地说,设置一个
宏字符
,以替换该运算符,例如,加法:

(set-macro-character #\! (lambda (stream char)
                           (declare (ignore char))
                           (cons '+ (let ((next (read stream t nil t)))
                                 (if (consp next) next
                                     (list next)))))
CL-USER> !1
1
CL-USER> !(1 2)
3
另一种更复杂、更灵活的方法是定义一个宏,在其中应用自定义转换,将连接的前缀代码转换为包含的前缀。这个技巧使用了这样一个事实,普通的Lisp阅读器将以同样的方式读取数据,
foo(bar)
foo(bar)
,即它将它们分成两个元素

此类宏的简单版本可能如下所示:

(defmacro with-prefix-syntax (&body body)
  `(progn ,@(loop :for tail :on body :while body
                  :collect (if (and (not (atom (second tail)))
                                    (fboundp (first tail)))
                               (prog1 (cons (first tail) (second tail)
                                 (setf tail (rest tail)))
                               (first tail)))))
它将仅转换顶级表单:

CL-USER> (macroexpand-1 '(with-prefix-syntax
                           print(1)))
(PROGN (PRINT 1))
CL-USER> (macroexpand-1 '(with-prefix-syntax
                           1))
(PROGN 1)
CL-USER> (macroexpand-1 '(with-prefix-syntax
                           print(1)
                           2))
(PROGN (PRINT 1) 2)
但不适用于较低级别:

CL-USER> (macroexpand-1 '(with-prefix-syntax
                           print(1)
                           (+ print(2))))
(PROGN (PRINT 1) (+ PRINT (2)))

尽管递归地变换所有层很容易(这是留给读者的练习:)

我想你的意思是
(设置宏字符#\!…
:)谢谢你给出了一个有趣的答案。让这一个坐两周,看看我们还能得到什么…所以你的第二个方法基本上是建立一个上下文,然后重写其中的所有代码。那很好。它确实需要查看上下文中的所有代码两次,这可以为程序编译出来,但我想,对大数据进行运行时解释的成本会稍高一些。不过,这是你付出的代价。很酷。谢谢。我不熟悉read宏,将不得不查找这些宏,这看起来更接近我的想法。这是只对独立原子进行黑客攻击,还是对读卡器进行黑客攻击会影响char的所有实例?如果我还有一个函数(not!ohno),中间有一个被黑客攻击的字符,会发生什么呢?它会把这个分割成一些奇怪的东西吗?谢谢。关于reader宏,它只会对以
开头的表单触发。关于代码重写宏,我忘了提到一个限制:当它检查宏扩展时符号是否是函数/宏时,它释放了普通Lisp的一些动态性。尽管如此,这应该是一个小小的不便,这不会影响大多数用例。另外,为了澄清这一点,它只需要对输入代码进行一次遍历。