Filippo Cremonese
Filippo Cremonese

Reputation: 53

Multiple ellipsis when defining syntax

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

Answers (2)

ceving
ceving

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

Alexis King
Alexis King

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

Related Questions