如何对使用syntax-parse捕获的可选属性进行分组?

时间:2015-12-31 20:12:24

标签: macros scheme racket

在编写使用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)))
                 ...))

但是,有一个问题:我希望能够这些选项分组为两个组:包含ab的组,以及包含{的组{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我自己的东西?

3 个答案:

答案 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-optssecond-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)]))