我正在尝试制作一个类似于 Common Lisp cl-ppcre 库中的 register-groups-bind 的宏。这个想法是,你用组创建一个正则表达式,并给它一个变量列表,要执行的语句。它将每个正则表达式组与其中一个变量绑定,然后使用这些绑定执行语句。例如:
(register-groups-bind
(s1 s2 s3)
("(\\w+) +(\\w+) +(\\w+)" "moe larry curly")
(list s2 s3 s1))
这将返回("larry" "curly" "moe")
。另一个复杂之处在于,在变量列表中,除了变量之外,您还可以指定一个在匹配的字符串上运行的函数,例如(register-groups-bind ((string->number x) ...
这样,它会在将匹配的值绑定到 x 之前对其运行 string->number。当您指定这样的函数时,您可以指定多个变量,例如:
(string->number x y z)
。
我正在尝试在创建 let 绑定的地方进行设置,如下所示:
(let ((s1 (match:substring m 1))
(s2 (match:substring m 2))
(s3 (match:substring m 3)))
... statements ...
有两件事我不明白如何使用 Scheme 的卫生宏功能。首先,我该如何处理变量列表可能包含变量名称的混合,或包含函数名称和变量的列表这一事实。其次,对于每个 let 绑定,我需要一个序列号来指示要获取哪个正则表达式组。
我可以使用 defmacro 在 Guile Scheme 中做到这一点,如下所示:
(defmacro regex-group-bind (vars re-match . statements)
(let* ((bind-var (gensym))
(converted (convert-var-list vars bind-var)))
`(let ((,bind-var ,re-match))
(if (regexp-match? ,bind-var)
(let (,@converted)
,@statements)
#f))))
Where
convert-var-list 是一个将变量列表转换为绑定列表的函数let
。调用时,convert-var-list
我需要传入包含正则表达式匹配结果的变量。输出示例如下convert-var-list
:
scheme@(guile-user)> (define match-var (gensym))
scheme@(guile-user)> (convert-var-list '(foo bar (string->number x y z)) match-var)
$9 = ((foo (match:substring #{ g5228}# 1))
(bar (match:substring #{ g5228}# 2))
(x (string->number (match:substring #{ g5228}# 3)))
(y (string->number (match:substring #{ g5228}# 4)))
(z (string->number (match:substring #{ g5228}# 5))))
scheme@(guile-user)>
我希望能够使用卫生宏来实现这一点,我假设我可能需要使用syntax-case
,也许还有像这样的函数convert-var-list
,但我不确定这是否是作弊。我需要使用syntax->datum
和datum->syntax
才能做到这一点吗?
register-groups-bind
您可以使用宏来实现 Scheme 版本syntax-rules
,但我认为 更好syntax-case
。下面使用辅助宏来构建let
赋值列表,每次使用给定列表中的一个变量。它有语法的情况,即在匹配组字符串上调用函数并绑定该结果,以及仅使用裸变量名的情况。但它没有变量cl-ppcre
的行为nil
,只是跳过将相应的匹配组绑定到任何东西(但添加并不难,#f
因为这是Scheme)。这留给读者练习,提示语法模式可以包含文字值。这里的技巧和使用的目的
syntax-case
在于,每次分配一个变量时,当前匹配组的计数器都会作为宏扩展的一部分直接递增;syntax-rules
使用相同基本思想,但用替换#,(+ (syntax->datum #'counter) 1)
位(+ counter 1)
会有很多(match:substring match (+ (+ (+ 1 1) 1) 1)
等调用,因为syntax-rules
不允许您在扩展期间直接执行代码。