如何编写支持非标准调用语法的Common Lisp宏?
好问题!要在Common Lisp里实现@route(*app*, "/", :GET)这种类Python装饰器风格的语法,核心是用**读取器宏(Reader Macro)**来扩展Lisp的原生语法。下面是具体步骤和可运行的代码示例:
1. 核心思路:语法转换
我们需要让Lisp的读取器把非标准的@route(...)语法,自动转换成你已经定义好的define-route宏调用格式。简单来说就是:@route(*app*, "/", :GET) → (define-route (*app* "/" :GET))
2. 实现读取器宏函数
首先写一个函数,用来处理@开头的表达式,负责解析符号、清理参数并转换为标准Lisp形式:
(defun read-at-notation (stream char) (declare (ignore char)) ;; 读取@后面的符号(比如route) (let* ((macro-short-name (read stream t nil t)) ;; 读取括号内的所有内容,得到带逗号的参数列表 (raw-args (read-delimited-list #\) stream t)) ;; 移除参数中的逗号(Lisp不需要逗号分隔参数) (clean-args (remove-if (lambda (x) (eq x ',')) raw-args))) ;; 拼接成完整的宏名(route → DEFINE-ROUTE) (let ((full-macro-name (intern (concatenate 'string "DEFINE-" (string macro-short-name)) *package*))) ;; 返回标准宏调用形式:(define-route (arg1 arg2 arg3)) (list full-macro-name (if (= (length clean-args) 1) (car clean-args) clean-args)))))
3. 注册读取器宏
把@字符绑定到上面的处理函数,让Lisp读取器识别这个新语法:
(set-macro-character #\@ #'read-at-notation)
4. 测试你的新语法
现在你就可以用简洁的@route语法来定义路由了,和你原来的逻辑完全兼容:
@route(*app*, "/", :GET) (print "Welcome to ningle using GET!")
Lisp读取器会自动把它转换成你熟悉的标准宏调用:
(define-route (*app* "/" :GET) (print "Welcome to ningle using GET!"))
额外优化:支持关键字参数风格
如果想更贴近Python装饰器的写法,比如@route(app=*app*, uri="/", method=:GET),可以修改读取器宏函数来解析键值对:
(defun read-at-notation (stream char) (declare (ignore char)) (let* ((macro-short-name (read stream t nil t)) (raw-args (read-delimited-list #\) stream t)) (clean-args (remove-if (lambda (x) (eq x ',')) raw-args)) ;; 把app=*app*转换成:app *app*这种关键字参数形式 (parsed-args (loop for (key val) on clean-args by #'cddr collect (intern (string key) "KEYWORD") collect val))) (let ((full-macro-name (intern (concatenate 'string "DEFINE-" (string macro-short-name)) *package*))) (list full-macro-name parsed-args))))
此时你可以这样写路由:
@route(app=*app*, uri="/", method=:GET) (print "Welcome with keyword args!")
它会被转换成:
(define-route (:app *app* :uri "/" :method :GET) (print "Welcome with keyword args!"))
当然,你需要调整原有的define-route宏来支持关键字参数的解构逻辑。
注意事项
- 读取器宏是全局生效的,要注意和其他库的语法冲突(如果有其他库也用
@做读取器宏); - 字符串中的逗号不会被误处理,因为Lisp读取器会完整读取字符串内容;
- 如果只想在特定包中启用该语法,可以在读取器函数中加入包判断逻辑。
内容的提问来源于stack exchange,提问作者MadPhysicist
相关产品推荐
相关产品推荐

