[racket] How to communicate from macro to DrRacket tool ?
Here is a self-contained example that shows how one might do
communication; I believe something like this would work in the context
of the DrRacket evaluator as well, though I haven't tried it yet.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
#lang racket
;; Experiment: interaction between compiled code and a tool.
;;
;; Often, 3d syntax is often used here to get expanded code to communicate
;; with some other third party (like a tool).
;;
;; But let's say we don't want 3d syntax. Is there another way?
;;
;; Idea: when evaluating code, attach a module to the evaluating
;; namespace that the code and the tool share.
;;
(require syntax/kerncase)
(define ns (make-base-namespace))
(eval '(module tool-runtime racket/base
(provide (all-defined-out))
(define current-on-application
(make-parameter (lambda (stx)
(printf "default\n")
(void)))))
ns)
(eval '(require 'tool-runtime) ns)
;; Let us extract out an identifier syntax for the current-on-application
;; function:
;;
;; current-on-app-stx: identifier
(define current-on-app-stx (eval '#'current-on-application ns))
;; It will also be useful to get the actual parameter value, and not
just the syntax.
(define current-on-application (eval 'current-on-application ns))
;; If we inspect at current-on-app-stx, we'll see that it has an
identifier binding
;; attached to the tool-runtime module we're written above.
;; Let us try to use this in the context of annotating a bit of code
and injecting calls
;; to current-on-application. First, let's create the expanded code:
(define expanded-code
(parameterize ([current-namespace ns])
(expand '(begin (define (f x)
(* x x))
(define (hypo a b)
(sqrt (+ (f a) (f b))))
(displayln (hypo 3 4))))))
;; Now let's annotate this code.
;; We will put a reference to the tool-runtime's current-on-application
;; whenever we see an application.
;; Note: this sample annotator is woefully incomplete. We should
really follow the
;; grammar of fully-expanded syntax! This is just to sketch out the idea.
(define annotated-code
(let loop ([stx expanded-code])
(kernel-syntax-case stx #f
[(begin top-level-form ...)
#`(begin #,@(map loop (syntax->list #'(top-level-form ...))))]
[(define-values (id ...) expr)
#`(define-values (id ...) #,(loop #'expr))]
[_
(identifier? stx)
stx]
[(#%plain-lambda formals expr ...)
#`(#%plain-lambda formals #,@(map loop (syntax->list #'(expr ...))))]
[(#%plain-app expr ...)
#`(begin
((#,current-on-app-stx) #'#,stx)
(#%plain-app expr ...))])))
annotated-code
;; If we had been using 3d syntax, the annotated code would not be compilable.
;; But here, we can still compile the code. The compiler will know that the
;; references to current-on-application refer to our tool-runtime module, since
;; we're compiling it in the namespace that knows about it.
(define compiled-code
(parameterize ([current-namespace ns])
(compile annotated-code)))
;; Now, if we try to evaluate the code in a namespace that doesn't know about
;; that tool-runtime, of course bad things will happen: it will say that it has
;; no idea what tool-runtime is about:
;
; Uncomment the following to see the error:
#;(eval compiled-code (make-base-namespace))
;; But we can evaluate it in ns:
(printf "evaluating the compiled code\n")
(eval compiled-code ns)
(newline)
;; Most importantly, we should be able to also override the parameter:
(printf "overridding current-on-application:\n")
(parameterize ([current-on-application
(lambda (stx)
(printf "calling ~s\n" stx))])
(eval compiled-code ns))