How can I write a macro that recognizes arbitrary other macros?

37 views
Skip to first unread message

Ryan Kramer

unread,
Apr 16, 2020, 9:15:07 PM4/16/20
to Racket Users
The following code produces '(1 2 3 4)

(define-syntax (collect stx)
  (syntax-case stx (define)
    [(_ (define id val) rest ...)
     #'(let ([id val])
         (collect rest ...))]
    [(_ (macro arg ...) rest ...)
     (syntax-local-value #'macro (lambda () #f))
     (let ([expanded (local-expand #'(macro arg ...) 'expression (list #'define))])
       (println expanded)
       #`(collect #,expanded rest ...))]
    [(_ a rest ...)
     #'(cons a (collect rest ...))]
    [(_)
     #'(list)]))

(define-syntax-rule (my-define stuff ...)
  (define stuff ...))

(collect 1 2 (define x 3) x 4)


I would like `(collect 1 2 (my-define x 3) x 4)` to produce the same result. But instead I get the error message "let-values: cannot bind tainted identifier in: x"

"OK, maybe this is impossible" I thought, and moved on to what I should have been working on anyway. But now I see that `class*` does seem to recognize arbitrary macros. The following example shows that `class*` seemingly understands my arbitrary `define-foo` macro:

(define-syntax-rule (define-foo)
  (define/public (foo)
    (list this 'foo)))

(define my-class%
  (class* object% ()
    (super-new)
    (define-foo)))

(define c (new my-class%))
(send c foo)

How does `class*` understand my `define-foo` macro? And can the same technique be used to fix my `collect` macro?

Matthew Flatt

unread,
Apr 16, 2020, 10:50:45 PM4/16/20
to Ryan Kramer, Racket Users
The main trick in this case is to recognize `define-values` (which is
what `define` expands to) instead of `define`. That's because
`define-values` propagates syntax arming to its identifiers and
right-hand side, which means that your macro is allowed to pull it
apart.

You'll also need to use an expansion context other than 'expression,
though.
> --
> You received this message because you are subscribed to the Google Groups
> "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to racket-users...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/racket-users/0d01f4c1-f0b4-49cb-b9c2-fca7e2ca
> 6429%40googlegroups.com.

Ryan Kramer

unread,
Apr 17, 2020, 6:56:37 PM4/17/20
to Racket Users
Thanks! That was much easier than I was expecting.

For posterity, here is some working code but be warned that I don't know whether 'module is the best expansion context to use. But this is a toy example anyway.

#lang racket

(define-syntax (collect stx)
  (syntax-case stx (define-values)
    [(_ (define-values (id ...) expr) rest ...)
     #'(let-values ([(id ...) expr])

         (collect rest ...))]
    [(_ (macro arg ...) rest ...)
     (syntax-local-value #'macro (lambda () #f))
     (let ([expanded (local-expand #'(macro arg ...)
                                   'module
                                   (list #'define-values))])
       (syntax-case expanded (define-values)
         [(define-values stuff ...)
          #`(collect #,expanded rest ...)]
         [else
          #'(cons (macro arg ...) (collect rest ...))]))]

    [(_ a rest ...)
     #'(cons a (collect rest ...))]
    [(_)
     #'(list)]))

(define-syntax-rule (my-define stuff ...)
  (define stuff ...))

(define-syntax-rule (my-list stuff ...)
  (list stuff ...))

(collect 1 2 (my-define x 3) x (my-list 4 5))


Reply all
Reply to author
Forward
0 new messages