包含多个…的球拍语法模式



我正在使用类似unix的管道为球拍编写语法,如下所示:

> ("FOO" > string-replace "O" "E" > string-append "x" > string-downcase)
"feex"

这是一个蛮力解决方案,它支持具有2、1和0(额外)参数的过程:

(require (prefix-in racket/base/ racket/base) syntax/parse/define)
(define-syntax-parser #%app
[(_ data (~literal >) proc a b (~literal >) rest ...) #'(#%app (proc data a b) > rest ...)]
[(_ data (~literal >) proc a (~literal >) rest ...) #'(#%app (proc data a) > rest ...)]
[(_ data (~literal >) proc (~literal >) rest ...) #'(#%app (proc data) > rest ...)]
[(_ data (~literal >) proc rest ...) #'(#%app proc data rest ...)]
[(_ rest ...) #'(racket/base/#%app rest ...)])

问题是找到下一个管道,因为语法模式不允许多个…模式。宏需要知道关闭第一个表单的下一个管道在哪里。除非有一种方法来构建部分语法对象与不匹配的父?

我可以嵌套省略号,但是我必须使用额外的父字符:

(define-syntax-parser #%app
[(_ data (~literal >) (proc params ...) > rest ...) #'(#%app (proc data params ...) > rest ...)]
[(_ data (~literal >) proc rest ...) #'(#%app proc data rest ...)]
[(_ rest ...) #'(racket/base/#%app rest ...)])
> ("FOO" > (string-replace "O" "E") > (string-append "x") > string-downcase)
"feex"

有没有办法做到这一点没有额外的父母?

我知道clojure的线程宏,但是如果你必须嵌套它们,它们很难遵循。

编辑:这个问题的解决方案现在可以作为球拍包和github

您可以使用~seq模式与省略号组合来匹配没有父元素的模式。例如:

(define-syntax-parser split
[(_ (~seq a b) ...)
#'(list a ... b ...))

将要求为split提供偶数个参数,然后将这些参数重新排列并构建成一个列表:

(split 1 2 3 4 5 6)
; =>
(list 1 3 5 2 4 6)

当然,要注意~seq不是魔法,并且在解析时对回溯的支持有限。但原则上你应该能够做这样的事情:

(data (~seq (~literal >) proc args ...) ...)

@Leif Andersen的建议在args模式不允许>时效果最好,因此在参数上添加(~not (~literal >))可能会有所帮助:

(_ data (~seq (~literal >) proc (~and args (~not (~literal >))) ...) ...)

结合@Lief Andersen和@Alex Knauth的答案,我想出了这个解决方案。据我所知,没有办法避免递归,但也许在语法/解析中有一些我不知道的魔法。

(require (prefix-in base/ racket/base) syntax/parse/define)
(define-syntax-parser #%app
[(_ data (~seq (~literal >) proc (~and args (~not (~literal >))) ...)) #'(base/#%app proc data args ...)]
[(_ data (~seq (~literal >) proc (~and args (~not (~literal >))) ...) rest ...) #'(#%app (proc data args ...) rest ...)]
[(_ rest ...) #'(base/#%app rest ...)])

示例用法(注意第一个>是repl提示符,而不是代码):

> ("FOO" > string-downcase > string-replace "o" "e" > string-append "abc")
"feeabc"

添加对将数据传递到过程最后一个参数的支持也很简单:

(require (prefix-in base/ racket/base) syntax/parse/define)
(define-syntax-parser #%app
[(_ data (~seq (~literal >) proc (~and args (~not (~literal >)) (~not (~literal >>))) ...)) #'(base/#%app proc data args ...)]
[(_ data (~seq (~literal >>) proc (~and args (~not (~literal >)) (~not (~literal >>))) ...)) #'(base/#%app proc args ... data)]
[(_ data (~seq (~literal >) proc (~and args (~not (~literal >)) (~not (~literal >>))) ...) rest ...) #'(#%app (proc data args ...) rest ...)]
[(_ data (~seq (~literal >>) proc (~and args (~not (~literal >)) (~not (~literal >>))) ...) rest ...) #'(#%app (proc args ... data) rest ...)]
[(_ rest ...) #'(base/#%app rest ...)])

使用例子:

> ('(1 2 3) > first > * 3 >> list-ref '(a b c d e f))
'd

我对这个宏如何影响性能的评论很感兴趣。也许在代码中进行转换比在模式中进行转换更好。