How do I associate mangled identifiers with the original syntax?

Hi, Racket Discourse.

I am busy working on a macro that, among other things, expands into a struct with certain fields being made up of concatenations via . of the original syntax.

As an example, we have this little definition below, and some of the field accessors generated by the resulting struct.

Note how the original syntax is associated with the resulting field accessor.

Now, because the path parameter ip.geo is a compound, it is not the original syntax so is not associated in the same manner to the original geo.

I have taken a shallow look at define-struct.rkt but it is a hefty one. What in particular am I supposed to be looking at, to achieve this? Rename-transformers, maybe syntax source location data, are the vague directions I think I am headed to?

It's not super critical, I am just curious how that would be accomplished.

3 Likes

(From memory) you might be looking for something called sub-range-binders? I think format-id has some support for that.

3 Likes

That's really cool, thank you, @benknoble :grin:

I will have to mess around a bit to see what's what, but now I can look for examples of what others have done!


Edit: dit werk!

All that was needed (in this case), as @benknoble mentioned, was this:

(format-id stx "~a:~a" api-id name #:subs? #true)
2 Likes

As I've just reminded myself, sometimes what you want is actually to use #:source and the 'original-for-check-syntax property. Example:

#lang racket

(require syntax/parse/define
         (for-syntax racket/syntax))

(define-syntax-parser define-f*
  [(_ f:id)
   #:with f* (format-id #'f "~a*" #'f)
   #'(define f* 1)])

(define-f* g)
g*

DrRacket doesn't draw any arrows here. With #:subs? #t, we get an arrow from g in g* to the input g to the macro.

But what if we want an arrow from g* (the entire identifier) to the g in the macro? For that, we should use #:source #'f and the syntax property I mentioned (which is one of 2 ways DrRacket draws arrows; the other is for syntax-original? syntax), and we should omit #:subs? (or things get confused):

#lang racket

(require syntax/parse/define
         (for-syntax racket/syntax))

(define-syntax-parser define-f*
  [(_ f:id)
   #:with f* (syntax-property (format-id #'f #:source #'f "~a*" #'f)
                              'original-for-check-syntax #t)
   #'(define f* 1)])

(define-f* g)
g*

Now we have an arrow from g* to g, rather than just g to g.

This matters a bit more when building prefixed IDs, I think:

  • If a-b-c is derived from b with #:subs? arrows, then the only place my editor can jump-to-definition is when on b in a-b-c.
  • However, if a-b-c is derived from b with #:source + 'original-for-check-syntax arrows, then my editor can jump-to-definition from anywhere in a-b-c because the whole identifier gets the arrow.
2 Likes