2

我正在使用类似于 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获得

4

3 回答 3

4

您可以使用~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 ...) ...)
于 2021-01-15T17:37:38.283 回答
2

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

(_ data (~seq (~literal >) proc (~and args (~not (~literal >))) ...) ...)
于 2021-01-17T19:13:52.867 回答
1

结合@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

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

于 2021-01-18T17:04:51.963 回答