【问题标题】:How can I group optional attributes captured with syntax-parse?如何对使用语法分析捕获的可选属性进行分组?
【发布时间】:2016-04-05 16:59:03
【问题描述】:

在编写使用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 的组,以及包含x 和@ 的组987654327@。但是,用户仍然可以按任意顺序指定选项,因此对于本示例输入:

(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) #'()))))

这可以使用syntax/parse/experimental/template中的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 提供的内置函数来做到这一点?如果没有,是否有任何简单的方法来定义 ~group 我自己?

【问题讨论】:

    标签: macros scheme racket


    【解决方案1】:

    我(还)不确定是否可以使用~group 之类的方法来执行此操作,但是有一种方法可以使使用#:with 的现有(工作)解决方案看起来更好。也许它适用于你的情况,也许不是。

    ~optional 接受默认参数#:defaults,您可以将其设置为空语法列表#'#f 或其他一些标记值,从而消除在@987654328 中包含if 语句的要求@ 子句。它看起来像这样:

    (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)
    

    希望对您有所帮助。

    【讨论】:

    • 这对我不起作用,因为我没有要使用的默认值——我需要未提供给 not 的关键字参数显示在展开。
    【解决方案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 的第一个解决方案的作用相同,但它将这些内容抽象为可重用的模式扩展器。

    【讨论】:

      【解决方案3】:

      我认为使用~and 会导致最直接的宏,但~and 的头部模式版本限制性更强,并且不太有效,因此我将头部模式部分分开。

      下面的代码能实现你想要的吗?

      如果没有头部图案,你会丢失~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)]))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-04-21
        • 2016-03-07
        • 1970-01-01
        • 1970-01-01
        • 2015-12-19
        • 2022-01-11
        • 1970-01-01
        • 2021-11-25
        相关资源
        最近更新 更多