паттерн синтаксиса рэкета с несколькими s

Я работаю над синтаксисом для рэкета, используя каналы, похожие на 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, но им трудно следовать, если вам нужно их вложить.

РЕДАКТИРОВАТЬ: решение этой проблемы теперь доступно в виде пакета racket и на github


person Roger Keays    schedule 15.01.2021    source источник


Ответы (3)


Вы можете использовать шаблон ~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 ...) ...)
person Leif Andersen    schedule 15.01.2021

Предложение @Leif Andersen работает лучше всего, если шаблон args не допускает >, поэтому может помочь добавление (~not (~literal >)) в аргументы:

(_ data (~seq (~literal >) proc (~and args (~not (~literal >))) ...) ...)
person Alex Knauth    schedule 17.01.2021

Объединив ответы @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

Мне было бы интересно в комментариях, как этот макрос влияет на производительность. Может быть, было бы лучше сделать преобразование в коде, а не в шаблонах.

person Roger Keays    schedule 18.01.2021