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)
)
这有两部分:
- 在输入中接受
#:break
作为可选关键字
- 必要时在输出中生成
#:break
共有三种方法:多分支、可选和语法-class。
多分支
解决这个问题的最简单方法是在语法分析器中有两个分支。一个分支有关键字,另一个分支没有。
两个分支意味着两个模式匹配输入,两个模板产生输出。
(define-syntax-parser myfor
[(_ Binding #:break Break Form1 ...)
#'(for (Binding #:break Break) Form1 ...)]
[(_ Binding Form1 ...)
#'(for (Binding) Form1 ...)])
这相当简单明了,但缺点是您要重复自己,为每个分支多次指定 for
、Binding
和 Form1 ...
,然后得到如果添加更多关键字,会更详细。
可选和~?
另一种方法是在模式中使用 ~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
来说有点矫枉过正,但如果关键字行为比简单地传递关键字(如果存在)更复杂,则可能有必要。
我可以这样定义 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)
)
这有两部分:
- 在输入中接受
#:break
作为可选关键字 - 必要时在输出中生成
#:break
共有三种方法:多分支、可选和语法-class。
多分支
解决这个问题的最简单方法是在语法分析器中有两个分支。一个分支有关键字,另一个分支没有。
两个分支意味着两个模式匹配输入,两个模板产生输出。
(define-syntax-parser myfor
[(_ Binding #:break Break Form1 ...)
#'(for (Binding #:break Break) Form1 ...)]
[(_ Binding Form1 ...)
#'(for (Binding) Form1 ...)])
这相当简单明了,但缺点是您要重复自己,为每个分支多次指定 for
、Binding
和 Form1 ...
,然后得到如果添加更多关键字,会更详细。
可选和~?
另一种方法是在模式中使用 ~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
来说有点矫枉过正,但如果关键字行为比简单地传递关键字(如果存在)更复杂,则可能有必要。