Scheme/Racket: 如何将关键字添加到 define-syntax-parser

Scheme/Racket: How to add keyword to define-syntax-parser

我可以这样定义 for 循环的语法:

(require syntax/parse/define)
(define-syntax-parser myfor
  [(_ Binding Form1 ...)
   #'(for (Binding) Form1 ...)]
)

(myfor (I (range 0 10))
  (displayln I)
)

它有效,但是当我向它添加关键字时,它不遵循 #:break 关键字中的条件:

(require syntax/parse/define)
(define-syntax-parser myfor
  [(_ Binding #:break Break Form1 ...)
   #'(for (Binding) Form1 ...)]
)

(myfor (I (range 0 10))
  #:break (= I 5)
  (displayln I)
)

如何以正确的方式向 define-syntax-parser 添加关键字?

我找到了方法,我需要在语法中添加 ~optional 和 ~seq:

(require syntax/parse/define)
(define-syntax-parser myfor
  [(_ Binding {~optional {~seq #:break Break} #:defaults [(Break #'#f)] } Form1 ...)
   #'(for (Binding #:break Break) Form1 ...)]
)

(myfor (I (range 0 10))
  #:break (= I 5)
  (displayln I)
)

这有两部分:

  1. 在输入中接受 #:break 作为可选关键字
  2. 必要时在输出中生成 #:break

共有三种方法:多分支、可选和语法-class。

多分支

解决这个问题的最简单方法是在语法分析器中有两个分支。一个分支有关键字,另一个分支没有。

两个分支意味着两个模式匹配输入,两个模板产生输出。

(define-syntax-parser myfor
  [(_ Binding #:break Break Form1 ...)
   #'(for (Binding #:break Break) Form1 ...)]
  [(_ Binding Form1 ...)
   #'(for (Binding) Form1 ...)])

这相当简单明了,但缺点是您要重复自己,为每个分支多次指定 forBindingForm1 ...,然后得到如果添加更多关键字,会更详细。

可选和~?

另一种方法是在模式中使用 ~optional~seq 来匹配输入,同时在模板中使用 ~?~@ 来生成输出。

(define-syntax-parser myfor
  [(_ Binding {~optional {~seq #:break Break}} Form1 ...)
   #'(for (Binding {~? {~@ #:break Break}}) Form1 ...)])

注意模板中的 ~? 是模式中 ~optional 的位置,而模板中的 ~@ 是模式中 ~seq 的位置。

当输入模式和输出模板之间存在明显的对称性时,此策略最佳。

语法-class

(begin-for-syntax
  (define-splicing-syntax-class maybe-break-clause
    [pattern {~seq} #:with (out ...) '()]
    [pattern {~seq #:break Break} #:with (out ...) #'(#:break Break)]))

(define-syntax-parser myfor
  [(_ Binding mbc:maybe-break-clause Form1 ...)
   #'(for (Binding mbc.out ...) Form1 ...)])

在这种情况下,此策略对于 #:break 来说有点矫枉过正,但如果关键字行为比简单地传递关键字(如果存在)更复杂,则可能有必要。