0
votes

So I'm trying to write a macro to give class declarations some syntax I like better.

(define-syntax (defclass stx)
  (syntax-parse stx #:literals ((*...* ...))
    ;=============================
    [(defclass (name:id inits ... rest:id *...*) opts ...)
    #'(defclass (name inits ... rest *...*) (opts ...))]

    [(defclass (name:id inits ... rest:id *...*) (opts ...))
    #'(defclass (name inits ... rest *...*) object% ((super-new) opts ...))]

    [(defclass (name:id inits ... rest:id *...*) super (opts ...))
    #'(define name (class super (init inits ...) (init-rest rest) opts ...))]

    [(defclass (name:id inits ... rest:id *...*) super supers ... (opts ...))
    #'(define name (class* super (supers ...) (init inits ...) (init-rest rest) opts ...))]
    ;=============================
    [(defclass (name:id inits ...) opts ...)
    #'(defclass (name inits ...) (opts ...))]

    [(defclass (name:id inits ...) (opts ...))
    #'(defclass (name inits ...) object% ((super-new) opts ...))]

    [(defclass (name:id inits ...) super (opts ...))
    #'(define name (class super (init inits ...) opts ...))]

    [(defclass (name:id inits ...) super supers ... (opts ...))
    #'(define name (class* super (supers ...) (init inits ...) opts ...))]
    ;=============================
    [(defclass name:id opts ...)
    #'(defclass name (opts ...))]

    [(defclass name:id (opts ...))
    #'(defclass name object% ((super-new) opts ...))]

    [(defclass name:id super (opts ...))
    #'(define name (class super opts ...))]

    [(defclass name:id super supers ... (opts ...))
    #'(define name (class* super (supers ...) opts ...))]
    ;=============================
))

So (defclass (foo bar baz) (method X ...)) ultimately transforms to (define foo (class object% (super-new) (init bar baz) (method x ...))).

The problem is that DrRacket's background expansion seems to be expanding without end. Worse yet, when DrRacket tells me that it's been expanding for a very long time and would I like to see the steps so far, clicking the button to show the steps does not show the steps. I'm testing it with the line (defclass (char atts inventory level-atts)), which should generate (define char (class object% (init atts inventory level-atts) (super-new))), but as I said I'm unable to inspect the expansion process and see what's actually happening. I've also tried changing all the #'(defclass ...) templates for syntax-parse into just (defclass ...), in case syntax-parse doesn't expand the forms it returns, but then I run into the inability to refer to name, inits, etc, because they're not in a template.

1

1 Answers

1
votes

The simplest way to get "Background expansion pending..." is probably this:

(define-syntax-rule (infinite)
  (infinite))

(infinite)

infinite expands to infinite which expands to infinite... you get the idea.

Your defclass has many patterns that expand into defclass. One of the patterns must expand to itself.

There are a half dozen patterns here that expand to defclass. At a glance, I can't spot which one(s) have this problem. If you can't, either, I suggest working up step by step: Comment out all of the patterns. Next, start with the most-specific -- the "already most-expanded", which do not expand into defclass. Add them in one by one, with an example usage for each, until you hit the infinite expansion again. Then look hard at the pattern until you spot it.

(From speaking with a few of them, I get the impression that even people who are expert at Scheme and Racket macros work things out step by step. Non-trivial macros are not something where the full structure usually emerges from brain to finger correctly in one big blurp.)