Name of undefined identifier as string in macro

33 views
Skip to first unread message

Zelphir Kaltstahl

unread,
Aug 3, 2019, 4:48:50 AM8/3/19
to Racket Users
Hi!

I am trying to write a macro, which checks the name of an argument for presence a substring. This is not the main purpose of the macro, but I want to do different things depending on the substring being contained or not contained.

Here is what I've got so far:

~~~~~
;; A macro to get the identifier name as string, shamelessly copied from StackOverflow and renamed:

(define-syntax identifier-name->string
  (lambda (stx)
    (syntax-case stx ()
      ((_ id)
       (identifier? #'id)
       (datum->syntax #'id (symbol->string (syntax->datum #'id)))))))

;; And the actual macro I want to write:

(define-syntax define-api-route
  (lambda (stx)
    (syntax-case stx (GET HEAD POST PUT DELETE CONNECT OPTIONS TRACE PATH)
      [(_ route GET my-content-type)
       (string-contains? (identifier-name->string (syntax route)) "abc")
       (println "abc is in the identifier name")])))
~~~~~

With this, Racket will complain, that I am referencing an identifier before its definition:

~~~~~
> (define-syntax identifier-name->string
    (lambda (stx)
      (syntax-case stx ()
        ((_ id)
         (identifier? #'id)
         (datum->syntax #'id (symbol->string (syntax->datum #'id)))))))
>
  (define-syntax define-api-route
    (lambda (stx)
      (syntax-case stx (GET HEAD POST PUT DELETE CONNECT OPTIONS TRACE PATH)
        [(_ route GET my-content-type)
         (string-contains? (identifier-name->string (syntax route)) "abc")
         (println "abc is in the identifier name")])))
> (define-api-route abc/abc/abc GET application/json)                                                                 ; string-contains?: undefined;
;  cannot reference an identifier before its definition
;   in module: top-level
;   internal name: string-contains?
; [,bt for context]
~~~~~

If I take away the (syntax ...) in the guard expression however, it will also not work, as template variables may only occur in syntax:

~~~~~
> (define-syntax define-api-route                                                                                         (lambda (stx)                                                                                                           (syntax-case stx (GET HEAD POST PUT DELETE CONNECT OPTIONS TRACE PATH)                                                  [(_ route GET my-content-type)                                                                                         (string-contains? (identifier-name->string route) "abc")                                                              (println "abc is in the identifier name")])))
; readline-input:19:52: route: pattern variable cannot be used outside of a
;   template
;   in: route
; [,bt for context]
~~~~~

I have also tried loads of other stuff, but I cannot find a way to:

1. get the identifier name of route, whatever the user has as route (not a string yet!)
2. check inside the guard expression, whether the identifier name contains a certain substring
3. based on that substring call other macros to do the actual job of defining an API route

Can you help me writing this macro?
It would also be great, if the macro was portable, meaning that it is usable from any Scheme.

Ryan Culpepper

unread,
Aug 3, 2019, 5:53:25 AM8/3/19
to Zelphir Kaltstahl, Racket Users
There are two things going wrong here:

1. The undefined identifier is `string-contains?`. You are using it in
the macro body, so you must require the module that provides it for-syntax:

(require (for-syntax racket/string))

The reason you have to do that for `string-contains?` but not for
`identifier?`, `syntax`, `lambda`, and so on is that the racket language
implicitly does a `(require (for-syntax racket/base))` for you, and all
of those other things are provided by racket/base. (Technically, racket
just provides racket/base for-syntax.)

2. Your `identifier-name->string` macro needs to be a phase-1 function
instead.

The implementation of a macro is an expression at a phase 1 higher than
where the macro itself is defined. The top-level starts at phase 0, so
the right-hand side of the `define-api-route` is a phase-1 expression.
If you want to *use* (as opposed to *produce syntax referring to*)
`identifier-name->string` in the macro body, it must also be defined at
phase 1. Since you want to use it on a phase-1 *identifier value* and
get a phase-1 *string value*, it should be a function.

So replace the definition of `identifier-name->string` with this:

(begin-for-syntax
;; identifier-name->string : Identifier -> String
(define (identifier-name->string id)
(symbol->string (syntax->datum id))))

If you don't want to use `begin-for-syntax`, there are two other ways to
define this helper function and make it available to the macro body. You
can put it in a new module and require that module for-syntax (that is,
at phase 1). Or you can make the definition local to the
`define-api-route` macro by moving it just inside that macro's `(lambda
(stx) ___)`. In both cases, drop the `begin-for-syntax`.

Ryan
> --
> 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
> <mailto:racket-users...@googlegroups.com>.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/racket-users/8b706c81-2587-4dc8-aaaa-a900813571f1%40googlegroups.com
> <https://groups.google.com/d/msgid/racket-users/8b706c81-2587-4dc8-aaaa-a900813571f1%40googlegroups.com?utm_medium=email&utm_source=footer>.

Zelphir Kaltstahl

unread,
Aug 4, 2019, 10:23:35 AM8/4/19
to Ryan Culpepper, Racket Users
Thank you!

I am not 100% sure I understood all about the different phases, but I
seem to have semi-understood and am able to use my understanding
combined with a little trial and error.

I've now got it working as follows:

#+BEGIN_SRC racket
#lang racket

(require (for-syntax racket/string))

(define-syntax define-api-route
  (lambda (stx)
    (define (identifier-name->string id)
      (symbol->string (syntax->datum id)))

    (syntax-case stx (GET HEAD POST PUT DELETE CONNECT OPTIONS TRACE PATH)
      [(_ route GET my-content-type)
       (string-contains? (identifier-name->string (syntax route)) "abc")
       (syntax (quote aaa))]
      ;; an else branch basically
      [(_ route GET my-content-type) #t
       (syntax (quote bbb))])))
#+END_SRC

However, the actual code is supposed to define some procedures. This
would be quite long all in one
macro. So I want to call another procedure in the cases of syntax-case.
In this other macro, `identifier-name->string` will not be available, so
I want to give the other macro already the route as string, which will
be used to send requests to an API.

#+BEGIN_SRC racket
#lang racket

(require (for-syntax racket/string))

(define-syntax define-api-route
  (lambda (stx)
    (define (identifier-name->string id)
      (symbol->string (syntax->datum id)))

    (define (identifier->symbol id)
      (syntax->datum id))

    (define another-macro
      (lambda (route http-method my-content-type route-as-string)
        (syntax (quote bbb))))

    (syntax-case stx (GET HEAD POST PUT DELETE CONNECT OPTIONS TRACE PATH)
      [(_ route GET my-content-type)
       (string-contains? (identifier-name->string (syntax route)) "abc")
       (syntax (quote aaa))]
      ;; an else branch basically
      [(_ route GET my-content-type) #t
       (another-macro (syntax route)
                      'GET
                      (syntax my-content-type)
                      (identifier-name->string (syntax route)))])))
#+END_SRC

This works, but I was hoping to be able to not define everything inside
one macro, but instead split it up into multiple parts. Just imagine how
big that one macro could become. For example something like:

#+BEGIN_SRC racket
#lang racket

(require (for-syntax racket/string))

(define define-simple-api-route
  (lambda (route http-method my-content-type route-as-string)
    (syntax (quote bbb))))

(define-syntax define-api-route
  (lambda (stx)
    (define (identifier-name->string id)
      (symbol->string (syntax->datum id)))

    (define (identifier->symbol id)
      (syntax->datum id))

    (syntax-case stx (GET HEAD POST PUT DELETE CONNECT OPTIONS TRACE PATH)
      [(_ route GET my-content-type)
       (string-contains? (identifier-name->string (syntax route)) "abc")
       (syntax (quote aaa))]
      ;; an else branch basically
      [(_ route GET my-content-type) #t
       (define-simple-api-route (syntax route)
                                'GET
                                (syntax my-content-type)

                                (identifier-name->string (syntax
route)))])))
#+END_SRC

But then the procedure `define-simple-api-route` will not be defined for
use in the syntax-case.

You already mentioned the `begin-for-syntax`. However, it is Racket
specific and not available in for example Guile Scheme or other Schemes.
I guess I will have to put things in separate modules then. Or is there
any other way?
Reply all
Reply to author
Forward
0 new messages