在编写使用syntax/parse
的宏时,我创建了一个拼接语法类,用于捕获可能提供给宏的选项。这些选项都是可选的,它们可以按任何顺序提供。使用~optional
省略号头部模式使这很容易:
(define-splicing-syntax-class opts
(pattern (~seq (~or (~optional (~seq #:a a))
(~optional (~seq #:b b))
(~optional (~seq #:x x))
(~optional (~seq #:y y)))
...))
但是,有一个问题:我希望能够将这些选项分组为两个组:包含a
和b
的组,以及包含{的组{1}}和x
。但是,用户仍可以按任何顺序指定选项,因此对于此示例输入:
y
我希望能够产生以下属性:
(foobar #:b 3 #:y 7 #:a 2)
到目前为止,我已设法使用first-opts: (#:a 2 #:b 3)
second-opts: (#:y 7)
手动执行此操作,但它并不漂亮:
#:with
使用(define-splicing-syntax-class opts
#:attributes ([first-opts 1] [second-opts 1])
(pattern (~seq (~or (~optional (~seq #:a a))
(~optional (~seq #:b b))
(~optional (~seq #:x x))
(~optional (~seq #:y y)))
...)
#:with (first-opts ...)
#`(#,@(if (attribute a) #'(#:a a) #'())
#,@(if (attribute b) #'(#:b b) #'()))
#:with (second-opts ...)
#`(#,@(if (attribute x) #'(#:x x) #'())
#,@(if (attribute y) #'(#:y y) #'()))))
中的template
:
syntax/parse/experimental/template
然而,这实际上只是上面的一些糖,它实际上并没有解决必须枚举每个子句中的每个选项的问题。例如,如果我添加了(define-splicing-syntax-class opts
#:attributes ([first-opts 1] [second-opts 1])
(pattern (~seq (~or (~optional (~seq #:a a))
(~optional (~seq #:b b))
(~optional (~seq #:x x))
(~optional (~seq #:y y)))
...)
#:with (first-opts ...)
(template ((?? (?@ #:a a))
(?? (?@ #:b b))))
#:with (second-opts ...)
(template ((?? (?@ #:a x))
(?? (?@ #:b y))))))
选项,我需要记住将其添加到#:c
组,否则将完全忽略。
我真正想要的是一些声明性的方法来对这些可选值组进行分组。例如,我想要这样的语法:
first-opts
或者,更好的是,如果我可以使用现有的原语,那将是很好的,如下所示:
(define-splicing-syntax-class opts
#:attributes ([first-opts 1] [second-opts 1])
(pattern (~seq (~or (~group first-opts
(~optional (~seq #:a a))
(~optional (~seq #:b b)))
(~group second-opts
(~optional (~seq #:x x))
(~optional (~seq #:y y))))
...)))
然而,这些都不起作用。有没有办法使用(define-splicing-syntax-class opts
#:attributes ([first-opts 1] [second-opts 1])
(pattern (~seq (~or (~and first-opts
(~seq (~optional (~seq #:a a))
(~optional (~seq #:b b))))
(~and second-opts
(~seq (~optional (~seq #:x x))
(~optional (~seq #:y y)))))
...)))
提供的内置函数来做到这一点?如果没有,有没有简单的方法来定义syntax/parse
我自己的东西?
答案 0 :(得分:2)
有一种方法可以使用~groups-no-order
模式扩展器这样做:
(define-splicing-syntax-class opts
#:attributes ([first-opts 1] [second-opts 1])
[pattern (~groups-no-order
[first-opts
(~optional (~seq #:a a))
(~optional (~seq #:b b))]
[second-opts
(~optional (~seq #:x x))
(~optional (~seq #:y y))])])
(syntax-parse #'(foobar #:b 3 #:y 7 #:a 2)
[(foobar opts:opts)
(values #'(opts.first-opts ...)
#'(opts.second-opts ...))])
; #<syntax (#:a 2 #:b 3)>
; #<syntax (#:y 7)>
~groups-no-order
可以像这样定义:
#lang racket
(provide ~groups-no-order)
(require syntax/parse
seq-no-order
(for-syntax racket/syntax
syntax/stx))
(define-syntax ~groups-no-order
(pattern-expander
(lambda (stx)
(syntax-case stx ()
[(groups [group-name member-pat ...] ...)
(with-syntax ([ooo (quote-syntax ...)])
(define/with-syntax [[member-tmp ...] ...]
(stx-map generate-temporaries #'[[member-pat ...] ...]))
(define/with-syntax [group-tmp ...]
(generate-temporaries #'[group-name ...]))
#'(~and (~seq-no-order (~and (~seq (~var member-tmp) ooo)
member-pat)
... ...)
(~parse [[(~var group-tmp) ooo] ooo] #'[[member-tmp ooo] ...])
...
(~parse [group-name ooo] #'[group-tmp ooo ooo])
...))]))))
这与使用#:with
的第一个解决方案完全相同,但它将这些内容抽象为可重用的模式扩展器。
答案 1 :(得分:0)
我还不确定你能用~group
之类的方法做到这一点,但有一种方法可以让你使用#:with
的现有(工作)解决方案看起来像好多了。也许它适合你的情况,也许不适用。
~optional
接受默认参数#:defaults
,您可以将其设置为空语法列表#'#f
或其他一些标记值,从而删除您的if
要求您的#:with
子句中的1}}语句。它看起来像这样:
(define-splicing-syntax-class opts
#:attributes ([first-opts 1] [second-opts 1])
(pattern (~seq (~or (~optional (~seq #:a a) #:defaults ([a #'#f]))
(~optional (~seq #:b b) #:defaults ([b #'#f]))
(~optional (~seq #:x x) #:defaults ([x #'#f]))
(~optional (~seq #:y y) #:defaults ([y #'#f])))
...)
#:with (first-opts ...) #'(#:a a #:b b)
#:with (second-opts ...) #'(#:x x #:y y)
希望有所帮助。
答案 2 :(得分:0)
我认为使用~and
会导致最直接的宏,但是~and
的头部模式版本更具限制性并且不能正常工作,因此我将头部模式部分分开。 / p>
以下代码是否达到了您想要的效果?
如果没有头部模式,您将失去~optional
,因此我手动检查重复项。
此外,first-opts
和second-opts
并未展平,但我怀疑这没问题?
#lang racket
(require (for-syntax syntax/parse racket/list))
(define-for-syntax (check-duplicate-kws kws-stx)
(check-duplicates (syntax->list kws-stx) #:key syntax->datum))
(define-syntax test
(syntax-parser
[(_ (~seq k:keyword v) ...)
#:fail-when (check-duplicate-kws #'(k ...)) "duplicate keyword"
#:with ((~or (~and first-opts (~or (#:a _) (#:b _)))
(~and second-opts (~or (#:c _) (#:d _)))) ...)
#'((k v) ...)
#'(void)]))