Reputation: 53
I need to define a syntax for a fancy-sublist
procedure that works like this
> (fancy-sublist 1 2 -> 3 4 5 <- 6 7)
(3 4 5)
I tried to implement it by defining a new syntax
(define-syntax fancy-sublist
(syntax-rules (-> <-)
((_ x xs ... -> dis dis1 ... <- y ys ...)
(keep only the elements in the middle))))
But it seems I cannot put an ellipsis after another.
Is it possible to use define-syntax to do what I want?
Upvotes: 5
Views: 327
Reputation: 23871
Although you took an alternative approach, here is the answer to your original question.
The problem can be solved with syntax-rules
by splitting it into two parts:
fancy-head
fancy-tail
Both can be solved by recursion.
I am starting with the tail part, because it is a bit easier. Recursive macros need at least two rules: one for the termination and one for the recursion. First comes the termination and after that the recursion rule. If you do the recursion first, the macro will never stop. The condition for the termination is the occurrence of the token ->
in front of the tail. When we see that, we return the tail. Otherwise we throw away the first element and recurse with the rest.
(define-syntax fancy-tail
(syntax-rules (->)
;; termination
((_ -> tail ...)
(list tail ...))
;; recursion
((_ x tail ...)
(fancy-tail tail ...))
))
(fancy-tail 1 2 -> 3 4 5) ;; => (3 4 5)
The head part is a bit more complicated, because we have to accumulate the elements of the head, while we are searching for the token <-
. In order to accumulate we need an accumulator, which is an additional argument. Again we search by recursion, again we need two rules and again we start with the termination. The termination condition is, when we see the last element of the middle followed by the token <-
and followed by anything else. In that case we return the items of the accumulated head together with the item found last. In the recursive step we append the first item of the already accumulated items of the head and continue with the rest.
(define-syntax fancy-head
(syntax-rules (<-)
;; termination
((_ (head ...) item <- x ...)
(list head ... item))
;; recursion
((_ (head ...) item x ...)
(fancy-head (head ... item) x ...))
))
(fancy-head () 3 4 5 <- 6 7) ;; => (3 4 5)
Now you just have to put both together. Again the order is important. More specific rules have to be put at the beginning. Less specific rules go to the end. The rules for the head are more specific, because of the additional accumulator. So the head rules are at the beginning and the tail rules are at the end.
(define-syntax fancy-sublist
(syntax-rules (-> <-)
;; fancy-head
((_ (head ...) item <- x ...)
(list head ... item))
((_ (head ...) item x ...)
(fancy-sublist (head ... item) x ...))
;; fancy-tail
((_ -> tail ...)
(fancy-sublist () tail ...))
((_ x tail ...)
(fancy-sublist tail ...))
))
(fancy-sublist 1 2 -> 3 4 5 <- 6 7) ;; => (3 4 5)
Works in Chez, Chibi and Gambit out of the box.
Upvotes: 0
Reputation: 43902
Use the syntax/parse
library instead of syntax-rules
; it’s more capable in every way, and it produces considerably better error messages even when both can technically get the job done. I consider syntax-rules
a legacy feature from Scheme; syntax-parse
should really be the default choice in modern Racket. It copes with your example perfectly fine:
#lang racket
(require syntax/parse/define)
(define-syntax (<- stx)
(raise-syntax-error #f "cannot be used as an expression" stx))
(define-syntax-parser fancy-sublist
#:literals [<- ->]
[(_ x xs ... -> dis dis1 ... <- y ys ...)
#'(list dis dis1 ...)])
> (fancy-sublist 1 2 -> 3 4 5 <- 6 7)
'(3 4 5)
Upvotes: 5