[racket] [Racket Syntax] Check if identifier is lexically bound

From: Chrisophe Vandenberghe (chvndb at gmail.com)
Date: Tue Feb 25 16:32:02 EST 2014


I found a solution to the first problem where the parameter of the method are now recognised as "lexically bound". However, it seems like a dirty hack as i have to pass the arg list explicitly when checking the body expressions. Furthermore, this does not solve the issue when methods contain id bindings in the body. Any suggestions? I could keep a list during syntax parsing and whenever a define is found it adds to that list and then simply check if the id is in that list, but again that seems a very dirty hack.

#lang racket

(require (for-syntax syntax/parse))

(define-for-syntax (lexically-bound? stx)
  (define expanded (local-expand stx (syntax-local-context) #f))
  (and (identifier? expanded)
       (not (eq? #f (identifier-binding expanded)))))

(define-for-syntax (local-parameter? stx pars-stx)
  (ormap (lambda (par) (bound-identifier=? stx par)) (syntax-e pars-stx)))

(define-syntax (CLASS stx)
  ;; Literal
  (define-syntax-class (literal)
    (pattern value:boolean)
    (pattern value:char)
    (pattern value:integer)
    (pattern value:number)
    (pattern value:str))
  ;; Arg List
  (define-splicing-syntax-class (arg-list)
    (pattern (~seq arg:id ...)))
  ;; Class Expression (validates all expressions inside a class)
  (define-syntax-class (class-expr args)
    #:datum-literals (define)
    (pattern value:literal
             #:with <value> #'value)
    (pattern value:id
             #:with <value> (begin ;(display #'value) (newline)
                                   ;(display (lexically-bound? #'value)) (newline)
                                   ;(display (local-parameter? #'value args)) (newline)
                                   (if (or (lexically-bound? #'value)
                                           (local-parameter? #'value args))
    (pattern (define name:id (~var arg (class-expr args)))
             #:with <value> #'(define name arg.<value>))
    (pattern (operator:id (~var arg (class-expr args)) ...)
             #:with <value> #'(operator arg.<value> ...)))
  ;; Method Class
  (define-syntax-class (class-method)
      #:datum-literals (METHOD)
      (pattern (METHOD (name:id (~var arg-list arg-list)) (~var body (class-expr #'arg-list)) ...)
               #:with <value> #'(define (name arg-list.arg ...) body.<value> ...)))
  ;;;;; Class Parser
   #:datum-literals (CLASS)
   [(CLASS <method>:class-method)

 (METHOD (test a b) (define c 1) (list a b c d)))

(test 1 2)

On 25 Feb 2014, at 15:54, Chrisophe Vandenberghe <chvndb at gmail.com> wrote:

> Hey guys,
> (first time poster)
> I have been learning how the Racket Syntax Framework works and trying to make a custom language. What I am currently trying to make is a small Object Oriented language which has classes and so on. I don't want to use the built-in classes as I want to alter the OO meaning once I got my language going … Anyway, the goal would be to have something similar to this:
> 	(FIELD x 1)
> 	(FIELD y 1)
> 	(METHOD (x?) x)
> 	(METHOD (x! new) (set! x new)))
> Both fields and methods are kept in a hash table for lookup. So what I would want is that in the above method "x?" the identifier x is recognised as not "lexically bound" and thus refers to the field x. So during syntax transformation I can take some action to fetch the value of that field in the field lookup table. However, I can't seem to make the test for "lexically bound" work. It always returns false. So take for example the method "x!", in that case it would fail to recognise that the value of the identifier "new" is given by the local parameter and would also try to lookup new in the field lookup table of the object. However, it should only try to lookup x.
> I guess it's hard to grasp what I mean, so I have made a simplified version of what I have. Remember that I have stripped down most to make it more understandable, so this code does not create classes in any way but it is structured exactly like my original code. I might therefore seem weird to use so many syntax-classes as they are superfluous here, but in my implementation there is a lot more stuff going on which require them.
> #lang racket
> (require (for-syntax syntax/parse))
> (define-for-syntax (lexically-bound? stx)
>  (define expanded (local-expand stx (syntax-local-context) #f))
>  (and (identifier? expanded)
>       (not (eq? #f (identifier-binding expanded)))))
> (define-syntax (CLASS stx)
>  ;; Literal
>  (define-syntax-class (literal)
>    (pattern value:boolean)
>    (pattern value:char)
>    (pattern value:integer)
>    (pattern value:number)
>    (pattern value:str))
>  ;; Object Expression (validates all expressions inside a class)
>  (define-syntax-class (class-expr)
>    #:datum-literals (define)
>    #:commit
>    (pattern value:literal
>             #:with <value> #'value)
>    (pattern value:id
>             #:with <value> (begin (display #'value) (newline)
>                                   (display (lexically-bound? #'value)) (newline)
>                                   (if (lexically-bound? #'value) #'value #'(void))))
>    (pattern (define name:id arg:class-expr)
>             #:with <value> #'(define name arg.<value>))
>    (pattern (operator:id arg:class-expr ...)
>             #:with <value> #'(operator arg.<value> ...)))
>  ;; Method Class
>  (define-syntax-class (class-method)
>      #:datum-literals (METHOD)
>      (pattern (METHOD (name:id arg:id ...) body:class-expr ...)
>               #:with <value> #'(define (name arg ...) body.<value> ...)))
>  ;;;;; Class Parser
>  (syntax-parse 
>   stx
>   #:datum-literals (CLASS)
>   [(CLASS <method>:class-method)
>    #'<method>.<value>]))
> ;; Example Case
> (METHOD (test a b) (define c 1) a b c d))
> In the example above, what I want is that a, b and c are recognised as "lexically bound" and d is not. Then I can transform d to something that fetches the "d" entry in the fields hash map of the object. However, when this code is run all 4 are returned as unbound.
> If I would add:
> (define a 1)
> (define b 1)
> (define c 1)
> (define d 1)
> before the CLASS definition then it works, so the check to see if they are lexically bound should be correct. Therefore, I am thinking that I am approaching my problem incorrectly.
> Thanks for any response,
> Christophe
> p.s. If I make incorrect use of syntax-classes feel free to let me know.

Posted on the users mailing list.