Reputation: 69984
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
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
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
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