Hello ! I am tying to write 2 simple macros where one renames a identifier by appending $ to the beginning and end. The other returns the newly created identifiers in a list.
#lang racket
(require
(for-syntax syntax/parse
racket/syntax
syntax/stx))
(provide def id-list)
(define-syntax (def stx)
(syntax-parse stx
[(_ id:id val:expr)
#:with new-id (format-id #'id "$~a$" #'id)
#`(define new-id val)]))
(define-syntax (id-list stx)
(syntax-parse stx
[(_ ids:id ...)
#:with (new-ids ...) (stx-map (lambda (i) (format-id #`i "$~a$" i)) #`(ids ...))
#`(list new-ids ...)]))
(def x 10)
(def y 20)
(id-list x y)
This works as expected when I call the macro from within the module that it was created, however when I try the run the code from another module or submodule I get an unbound identifier error. I have a feeling this has to do with binding and phase levels, but after reading the docs I am still confused. Any help is appreciated.
-Josh
Example:
(module+ test
(def x 10)
(def y 20)
(id-list x y))
; $x$: unbound identifier
; in: $x$
; context...:
; #(1376184 macro) #(1376187 local) #(1376188 intdef) [common scopes]
; other binding...:
; #($x$ #<module-path-index=(submod 'test[2358842] test)> 0)
; #(1376160 module) [common scopes]
; common scopes...:
; #(1375539 module) #(1375546 module test) #(1376167 module test)
1 Like
In your id-list
macro, you have:
#
i`
in the call of format-id
. You probably meant to write just i
.
In def
, you need to put #'
in front of id
because id
is a syntax pattern. But in id-list
, i
is just a regular variable, so you should not put #'
in front of it.
2 Likes
Thank you for the explanation!
Try something like this:
#lang racket
(provide def id-list)
(require (for-syntax syntax/parse racket/syntax syntax/stx))
(begin-for-syntax
(define common-ctx #'common-context-for-def-and-id-list))
(define-syntax (def stx)
(syntax-parse stx
[(_ id:id val:expr)
#:with new-id (syntax-local-introduce (format-id common-ctx "$~a$" #'id))
#'(define new-id val)]))
(define-syntax (id-list stx)
(syntax-parse stx
[(_ ids:id ...)
#:with (new-ids ...) (stx-map (λ (i) (format-id common-ctx "$~a$" i))
#'(ids ...))
#'(list new-ids ...)]))
;(def x 10) (def y 20) (id-list x y)
(module+ test
(def x 10)
(def y 20)
(id-list x y))
The idea here is to make the common scope for identifiers in def
and id-list
explicit.
Roughly, every binding form and macro expansion creates a scope, and each fragment of syntax acquires a set of scopes that determines binding of identifiers within the fragment. In a language without macros, each scope set is identifiable by a single innermost scope. In a language with macros, identifiers acquire scope sets that overlap in more general ways.
When the identifier in id-list
is "recreated" the part of the scope from the macro expansion of def
is missing. The snippet above uses syntax-local-introduce
to fix that.
1 Like
It seems like @sorawee's replay was able to fix the issue. Is there a added benefit to creating a common scope for identifiers as your suggesting?
Also would you mind elaborating (or pointing me to some racket docs) that discuss the below in more detail?
identifiers acquire scope sets that overlap in more general ways.
Nothing more that making the connection between the two macros explicit.
I can recommend:
1 Like
Awesome! I will take a look at it. Thanks for all your help!
1 Like