hugomg
hugomg

Reputation: 69984

How do I expand inner macros before the outer ones?

Say I have the following macro:

(define-syntax-rule (qq x) '(1 x))

Can I make something that looks like (qq (qq 2)) expand into '(1 (1 2)) instead of (1 (qq 2) ?

The reason I say "looks like" is because so far the only hint I found suggests that inside-out macro expansion is tricky so I wonder if whats the saner way to do get my desired end result.

My original motivation has to do with Racket's parser generator library: to create a grammar the library provides a parser macro that looks like this:

(define my-parser (parser
  (start start) (end EOF)
  (tokens value-tokens op-tokens)
  (error (lambda (a b c) (void)))

  (grammar
   (start [(numbers) $1])

   (numbers [(numberx) (reverse $1)])
   (numberx [() empty]
            [(numberx NUM) (cons $2 $1)])
  )
))

My grammar has lots of boilerplate that I would like to abstract away. For example, I would love to be able to define some sort of list-rules abstraction that would let me write write something similar to

(define my-parser (parser
  (start start) (end EOF)
  (tokens value-tokens op-tokens)
  (error (lambda (a b c) (void)))

  (grammar
   (start [(numbers) $1])

   (list-rules NUM numbers numberx)
  )
))

However, if parser gets to expand first it will treat list-rules itself as a non-terminal instead of expanding it into the real non-terminals (numbers and numberx).

Upvotes: 2

Views: 418

Answers (3)

stchang
stchang

Reputation: 2550

You can try local-expand, though I admit I haven't spent the time to understand the second part of your question enough to know whether it's the right thing to use.

#lang racket
(define-syntax (qq stx)
  (syntax-case stx ()
    [(_ x) 
     (with-syntax ([y (local-expand #'x 'expression '())])
       #'`(1 ,y))]))
(qq 2)
(qq (qq 2))
(qq (qq (qq 2)))

=>

'(1 2)
'(1 (1 2))
'(1 (1 (1 2)))

Upvotes: 2

Greg Hendershott
Greg Hendershott

Reputation: 16260

Well, you could have the macro check for the shape:

(define-syntax qq
  (syntax-rules ()
    [(qq (qq x)) (list 1 (qq x))]
    [(qq x)      (list 1 x)]))

The good news is that this works "recursively":

(qq 2)           ; => '(1 2)
(qq (qq 2))      ; => '(1 (1 2))
(qq (qq (qq 2))) ; => '(1 (1 (1 2)))

But it will also work even if something other than qq is supplied:

(qq (+ 1))       ; => '(1 2)

If that's a problem for your intended use -- if you'd like that to be flagged as an error -- you could do it like this instead:

(define-syntax (rr stx)
  (syntax-case stx ()
    [(_ (rr x))
     (cond [(equal? 'rr (syntax->datum #'rr))
            #'(list 1 (rr x))]
           [else (raise-syntax-error #f "Expected rr" stx #'rr)])]
    [(_ x)
     #'(list 1 x)]))

Same results for the examples, except the last now gives a syntax error:

(rr 2)           ; => '(1 2)
(rr (rr 2))      ; => '(1 (1 2))
(rr (rr (rr 2))) ; => '(1 (1 (1 2)))
(rr (+ 1))       ; =>
; src/scheme/misc/so-syntax.rkt:27:5: rr: Expected rr
;  at: +
;  in: (rr (+ 1))

Update: The second version could be written a bit more cleanly using syntax-parse:

(require (for-syntax syntax/parse))
(define-syntax (rr stx)
  (syntax-parse stx
    [(_ ((~literal rr) x)) #'(list 1 (rr x))]
    [(_ (_ x)) (raise-syntax-error #f "Expected rr" stx #'rr)]
    [(_ x) #'(list 1 x)]))

Upvotes: 1

uselpa
uselpa

Reputation: 18937

You need to have 2 syntax rules, with the more general rule first; also not the use of the quasiquote and unquote operators so that the inner form can be evaluated:

(define-syntax qq
   (syntax-rules ()
     ((_ (x ...) ...)  `(1 ,(x ...) ...))
     ((_ x)            `(1 x))))

such as

-> (qq x)
'(1 x)
-> (qq (qq x))
'(1 (1 x))
-> (qq (qq (qq x)))
'(1 (1 (1 x)))
-> (qq (qq (qq (qq y))))
'(1 (1 (1 (1 y))))

This is also composable:

(define-syntax qq
   (syntax-rules ()
     ((_ (x ...) ...)  `(1 ,(x ...) ...))
     ((_ x)            `(1 x))))

(define-syntax hh
   (syntax-rules ()
     ((_ (x ...) ...)  `(2 ,(x ...) ...))
     ((_ x)            `(2 x))))

such as

-> (qq (hh x))
'(1 (2 x))
-> (hh (qq x))
'(2 (1 x))

Upvotes: 1

Related Questions