Syntax Common Lisp:如何创建连接前缀宏,类似于';?
通用Lisp宏通常使用包含的前缀表示法:(运算符填充…) 但是,特殊quote宏“使用串联前缀表示法:运算符stuff,或者运算符(stuff) 我想在SBCL Common Lisp中创建一个自定义宏,我们称之为!,它使用连接前缀语法对以下列表(甚至原子)进行操作,方式与“”类似。因此,我可以在任何地方调用它,例如(setqfoo!(bar)),而不必将它用中缀括在括号中 如何做到这一点?defmacro语法是什么样子的?谢谢。我有两种方法 第一个简单的变体是合适的,如果您只需要一个或数量非常有限的案例,并且只使用一个符号作为前缀(如示例案例中的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语法是什么样子的?谢谢。我有两种方法 第一个简单的变体是合适的,如果您只需要一个或数量非常
!
)。您可以创建一个读取宏,更具体地说,设置一个宏字符
,以替换该运算符,例如,加法:
(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的一些动态性。尽管如此,这应该是一个小小的不便,这不会影响大多数用例。另外,为了澄清这一点,它只需要对输入代码进行一次遍历。