[racket] How to implement declations in an internal definition context?

From: Jens Axel Søgaard (jensaxel at soegaard.net)
Date: Fri Jun 13 10:24:45 EDT 2014

Hi All,

In order to add linear equation syntax to MetaPict I need to
understand how implement declarations in an internal definition
context. For practice purposes I have written the following small
program. So far global declarations and local declaration via a
let-var binding construct works. I am now attempting to transfer the
methods used in let-var into an implementation of declarations that
work in an internal definition context. Unfortunately I am stuck.

In the definition of let-var I have used syntax-parameterize and I
can't figure what to do in an internal definition context.

Any help is appreciated. I have inserted and attached the program
below, but there is a pretty, color version at PasteRack:

     http://pasterack.org/pastes/9771

/Jens Axel

#lang racket
(require racket/splicing racket/stxparam)
(require (for-syntax syntax/parse racket/format))

;;;
;;; IMPLEMENTED
;;;

;;; This program implement three forms:
;;;   (declare x)              that declares that the symbol x is the
name of a variable
;;;   vars                     which evaluates to a list of all
declared variables
;;;                            at the point, where vars occurs
;;;   (let-var (x ...) body)   a local declaration, where the symbols x ...
;;;                            are declared in body, but not outside.

;;; Example

; Form:                               Result:
;   vars                               ()
;   (declare x)
;   vars                               (x)
;   (let-var (y) vars)                 (y x)
;   vars                               (x)
;   (let-var (y) (let-var z 1) vars)   (y x)

;;;
;;; PROBLEM
;;;

; i) How can I extend the definition of declare in order to
;    support declarations in a internal definition context?

; That is, is there a way to get this interaction:

; Form:                                                 Result:
;   vars                                                 ()
;   (declare x)
;   vars                                                 (x)
;   (let () (declare y) vars)                            (y x)
;   vars                                                 (y)
;   (let ()
;     (declare y)
;     (let () (declare z) 1)
;     vars)                                              (y x)


(begin-for-syntax
  ; Global variables are simply stored in a compile time variable
  (define globals '()))

; At expansion start there is only global variables, so
; vars simply returns a quoted copy of the symbols collected in globals
(define-syntax-parameter vars (λ(stx) #`'#,globals))

; Local variables on the other hand must be kept in a syntax parameter
; to keep the list of locally declared variable around at syntax
transformation time.
(define-syntax-parameter locals '())


(define-syntax (orig-declare stx)
  ; A simple version of declare (here named orig-declare) simply adds
  ; declared variables to the global list.
  (syntax-parse stx
    [(_ v)
     (define var (syntax->datum #'v))
     (unless (member var globals)
       (set! globals (cons var globals)))
     #'(void)]))


; The following tests reveals that everything works as expected:

; Expression     Expected
vars                ;  ()
(orig-declare x)    ;
vars                ;  (x)
(orig-declare x)    ;
vars                ;  (x)
(orig-declare y)    ;
vars                ;  (y x)


; Locally declared variables are implemented using syntax-parameterize.
; Both the user facing syntax   vars  and the list of locally
; declared are adjusted with the help of syntax-parameterize.

; (let-var (var ...) body ...)
;   evaluate body in an environment where var ... are declared variables
(define-syntax (let-var stx)
  (syntax-parse stx
    [(_ () body ...)
     #'(let () body ...)]
    [(_ (v:id vs:id ...) body ...)
     (let ()
       (define var (syntax->datum #'v))
       (define old-locals (syntax-parameter-value #'locals))
       (define new-locals (cons var old-locals))
       #`(syntax-parameterize ([vars (λ (stx) #''#,(append new-locals
globals))])
           (syntax-parameterize ([locals '#,new-locals])
             (let-var (vs ...) body ...))))]))


; Expression                             Expected
(let-var (a) vars)                    ;  (  a y x)
(let-var (a b) vars)                  ;  (b a y x)
(let-var (a) (let-var (b) vars))      ;  (b a y x)
(let-var (a) (let-var (b) 7) vars)    ;  (  a y x)


;;;
;;; The hope was to copy the ideas used in let-var to implement
;;; declarations in an internal definition context.

;;; The point where I am stuck:
;;;   syntax-parameterize can adjust the meaning of locals and vars in
a given body
;;;   But in an internal definition context, the meaning of locals and vars
;;;   must be adjusted in the rest of the internal definition context,
;;;   so at the point where (declare x) is expanded, there is no
access to the "body".

(define-syntax (declare stx)
  (syntax-parse stx
    [(_ v)
     (define var (syntax->datum #'v))
     (define ctx (syntax-local-context))
     (cond
       [(eq? ctx 'top-level)
        ; outside any module, definition or expression
        ; (except as a subsexpression in a top-level begin)
        ; i.e. think repl...
        (error 'declare "the top-level context not supported (yet?)")]
       [(eq? ctx 'module-begin)
        ; inside the body of a module as the only form within the module
        (error 'declare "the module-begin context not supported (yet?)")]
       [(eq? ctx 'expression)
        ; in a context where only expressions are allowed
        (error 'declare "declarations can not appear as expressions")]
       [(eq? ctx 'module)
        ; in a module (inside the module-begin layer)
        (unless (member var globals)
          (set! globals (cons var globals)))
        #'(void)]
       [(list? ctx) ; internal definition context
        ; in a nested context that allows both definitions and expressions
        ; e.g. the body of a let expression is an internal definition context
        ; For let and friends, a defintion in an internal definition context
        ; is a equivalent to local binding via letrec-syntaxes+values.
        ; Each body is partially expanded into one of:
        ;   i) define-values
        ;  ii) define-syntaxes
        ; iii) primitive expression other than begin
        ;  iv) a begin form (which is spliced in, and the newly-spliced subforms
        ;      are also partially expanded)
        ; The definitions and expressions are then rewritten into a
letrec-syntaxes+values form

        ; Note: A (define-syntax-parameter ...) expands into define-syntaxes.
        ;       But what about syntax-parameterize ?
        (define var (syntax->datum #'v))
        (define old-locals (unbox (syntax-parameter-value #'locals)))
        (define new-locals (cons var old-locals))
        (displayln new-locals)
        #'(void) ; <- What to write here?
        ]
       [else (error 'declare (~a "expansion reached unhandled
expansion context " ctx))])]))
-------------- next part --------------
A non-text attachment was scrubbed...
Name: example-of-declare-and-letvar.rkt
Type: application/octet-stream
Size: 6257 bytes
Desc: not available
URL: <http://lists.racket-lang.org/users/archive/attachments/20140613/2a583124/attachment-0001.obj>

Posted on the users mailing list.