Search code examples
macrosschemer6rschez-scheme

Check if an identifier is lexically bound at the point of macro expansion?


This is probably a bit of a newbie question, but I'm trying to write a macro that detects whether an identifier is lexically bound at the point of macro expansion, and changes its output accordingly. Is this possible in R6RS Scheme, and if so, how?

Why I Want to Know

I'm writing a toy Objective-C binding layer in Chez Scheme, using macros, and my initial challenge is in dealing with Objective-C selectors efficiently. The program has to query the Objective-C runtime, at runtime, for the actual SEL object corresponding to each selector name. The selector names used in the program will be known statically, during expansion, and it's easy enough to have my macros insert that query code, but I want to avoid repeat queries for the same selector name.

My first thought on this is to have some naming convention for Scheme definitions bound to SEL foreign objects. That way, I could have one (define) for each unique selector, and thus one runtime query per selector. This hinges on my macros being able to detect these bindings for any given selector, and introducing them if they do not already exist, hence my question.

This solution is still imperfect, since my macros may be expanded in inner scopes, but it was the most obvious to me. Is there a better way to "intern" expressions at expansion time?


Solution

  • I'm not entirely sure if this can be achieved by well specified behaviour but you can do it like the following:

    #!r6rs
    (library (bound helper)
        (export make-binding-check)
        (import (rnrs))
    (define-syntax make-binding-check
      (lambda (x)
        (syntax-case x ()
          ((_ if bound?)
           #'(begin
               (define-syntax if
                 (lambda (xx)
                   (syntax-case xx ()
                     ((_ b then els)
                      (free-identifier=? (datum->syntax #'if (syntax->datum #'b)) #'b)
                      #'els)
                     ((_ b then els)
                      #'then))))
               (define-syntax bound?
                 (lambda (xx)
                   (syntax-case xx ()
                     ((_ b)
                      #'(if b #t #f))))))))))
    )
    (library (bound)
        (export bound? if-bound)
        (import (bound helper))
    (make-binding-check if-bound bound?)
    )
    
    (import (rnrs) (bound))
    
    (display (bound? foo)) ;; #f
    (let ((foo 1))
      (display (bound? foo))) ;; #t
    (newline)
    

    The idea is using free-identifier=? to check if the given identifier is bound or not. The make-binding-check macro takes 2 identifiers and both of them should be unbound when the macro is expanded. To make such unbound identifiers, the code consist with 2 part, implementation and environment: The first one is (bound helper) which provides the implementation of the identifier comparison. The other one is (bound) which provides almost no binding environment.

    The comparison is done with the identifier passed from environment library and actual identifier you want to check. If the actual identifier is bound to anything, then it wouldn't be the same as unbound identifier, so bound? should return #f. (If you define make-binding-check and check, it'd return #t since it's defined in the (bound) library.)

    CAUTION This may or may not work depending on the implementation you are using, and I'm not sure if this works on Chez.