Nested ellipsis macro doesn't work in Guile and Racket

317 Views Asked by At

I'm trying to create a simple nested macro. It works in my Scheme implementation, but fails to run in Guile and Racket.

(define-syntax foo
  (syntax-rules (:c)
    ((_ x ...)
     (let-syntax ((bar (syntax-rules ::: (:c)
                         ((_ x)
                          (print x))
                         ((_ a b :::)
                          (begin
                            (display a)
                            (display " ")
                            (bar b :::))))))
       (bar x ...)))))
  • Guile throws:

syntax: missing ellipsis

  • Racket throws:

missing ellipsis with pattern variable in template

I've also tried to run in Gambit, but that one just throws:

Unbound variable: define-syntax

I guess you need to use a library to use a basic scheme.

In Checken Scheme, after updating ellipsis:

(define-syntax foo
  (syntax-rules (:c)
    ((_ x ...)
     (let-syntax ((bar (syntax-rules <:::> (:c)
                         ((_ x)
                          (print x))
                         ((_ a b <:::>)
                          (begin
                            (display a)
                            (display " ")
                            (bar b <:::>))))))
       (bar x ...)))))

throws:

template dimension error (too few ellipses?): x

What's wrong with this macro? Why does it throw an error?

EDIT:

It seems that this pattern is not valid:

(_ x ...)

but this is

(_ x y ...)

Is this specified somewhere? Why the first syntax is not valid?

Just to be complete, this code compiles, but why the first doesn't?

(define-syntax foo
  (syntax-rules ()
    ((_ x y ...)
     (let-syntax ((bar (syntax-rules <:::> ()
                         ((_ x)
                          (print x))
                         ((_ a b <:::>)
                          (begin
                            (display a)
                            (display " ")
                            (bar b <:::>))))))
       (bar x y ...)))))

But it doesn't work when tried to use foo macro. It throws:

unbound variable: bar

even when using letrec-syntax.

3

There are 3 best solutions below

0
On BEST ANSWER

The problem is that ellipsis are following x in your outer macro. This means the template that is produced should also have ellipsis following x, which it doesn't. If you rename the inner macro's x to y it should work as you expect.

In other words, what you're doing here is equivalent to:

(define-syntax foo
  (syntax-rules ()
    ((_ x ...)
     (display x))))

Which wouldn't be allowed either. If you have x with ellipsis in the pattern, you must also consume those ellipsis when consuming x in the template that follows it.

The reason it works if you make it (x y ...) is that y (and its ellipsis rest) is not consumed at all by the template.

4
On

This pattern (_ x ...) in the outer syntax-rules will bind the pattern variable x into a list of matches. That means, that when x appears in the template, it must be followed by (not necessarily directly) an ellipsis.

This why you get the errors from Guile and Racket.

Now consider the situation where the outer macro (foo) introduces an inner macro (bar), whose definition contains an ellipsis. When the expander sees ... does it belong to the outer or inner macro definition?

The convention used (at least by Racket and all psyntax-derived implementations of syntax-rules) is that ... belongs to the outer layer. In order to produce an "inner ellipsis" it needs to be "quoted". So so generate an inner ellipsis, write (... ...).

In practise this can be annoying, so I often wrap an inner definitions in

(with-syntax ((ooo (... ...))
 code using ooo as an inner ellipsis here)

As far as I can tell, the (syntax-rules <:::> () etc) in your example is non-standard, since the syntax is (syntax-rules literals (pattern template) ...). So the symbol <:::> is not allowed as the list of literals.

1
On

There are several issus with your macro.

  1. Reusing the lexical macro bindings in the template of the inner macro. x will be expanded in the result so (_ x) in the let-syntax will be tried replaced, however since x template has ellipses it is expected that the places x is inserted has ellipses somewhere. I do not think you meant for this to be the same binding so replacing it with any other symbol will fix it. eg. single

  2. Using let-syntax means you cannot have expansions that use the same syntax rule, but in the second pattern you print the first element and then do that. Replacing it with letrec-syntax will fix that.

  3. The pattern x ... matches zero or more elements. Thus (foo) matches the pattern, however bar does not have a compatible template. Adding a pattern to catch the no operands situation fixes this.

  4. Using SRFI-46 is not supported in Racket. You simply cannot expect it to be supported out of the box so you need to check that per implementation. in Racket you can use (... ...) as a replacement. This is the same as for R6RS / R7RS as well.

Taking these in I end up with this. that works well in Racket:

(define-syntax foo
  (syntax-rules (:c)
    ((_) (begin)) ; or you may signal an error 
    ((_ x...)
     (letrec-syntax ((bar (syntax-rules (:c)
                            ((_ single)
                             (print single))
                            ((_ first rest (... ...))
                             (begin
                               (display first)
                               (display " ")
                               (bar rest (... ...)))))))
       (bar x ...)))))

While I don't know how simplified your example is, this can be replaced by this:

(define-syntax foo
  (syntax-rules ()
    ((_ x ...)
     (begin
       (begin
         (display x)
         (display " "))
       ...))))