Metal Language Module in Racket

I am trying to write in Racket a metalanguage of the mylang module, which accepts a second language into which the modified body passes, such that

 (module foo mylang typed/racket body) 

equivalent to:

 (module foo typed/racket transformed-body) 

where the typed/racket can be replaced with any other module language, of course.

I tried a simple version that left the body unchanged. It works fine on the command line , but when launched in DrRacket gives the following error:

 /usr/share/racket/pkgs/typed-racket-lib/typed-racket/typecheck/tc-toplevel.rkt:479:30: require: namespace mismatch; reference to a module that is not available reference phase: 1 referenced module: "/usr/share/racket/pkgs/typed-racket-lib/typed-racket/env/env-req.rkt" referenced phase level: 0 in: add-mod! 

Here is the whole code:

 #lang racket (module mylang racket (provide (rename-out [-#%module-begin #%module-begin])) (require (for-syntax syntax/strip-context)) (define-syntax (-#%module-begin stx) (syntax-case stx () [(_ lng . rest) (let ([lng-sym (syntax-e #'lng)]) (namespace-require `(for-meta -1 ,lng-sym)) (with-syntax ([mb (namespace-symbol->identifier '#%module-begin)]) #`(mb . #,(replace-context #'mb #'rest))))]))) (module foo (submod ".." mylang) typed/racket/base (ann (+ 1) Number)) (require 'foo) 

Requirements (i.e. solutions that I would prefer to avoid):

  • Adding (require (only-in typed/racket)) inside the mylang module does the job, but I'm interested in a general solution where mylang doesn't need to know about typed/racket in al (i.e. if someone adds a new language foo , then mylang should work with it out of the box).
  • Also, I'm not interested in the tricks that declare a submodule, and then require and ref provide it, as it is done here , because it changes the path to the actual module (therefore main and test will lose their special behavior, for example).

    It is also slower during compilation, since submodules get visited and / or instance more times (this can be seen from the entry (begin-for-syntax (displayln 'here)) and has a noticeable effect on large typed/racket programs.

  • Bonus points if arrows in DrRacket work for built-in modules provided by a delegated language, for example. have arrows from ann , + and Number to typed/racket/base in the above example.

+8
racket require metalanguage
source share
1 answer

One thing you can do that, I believe, does not violate your requirements, is put into a module, extend that module completely and then map to #%plain-module-begin to insert a query.

 #lang racket (module mylang racket (provide (rename-out [-#%module-begin #%module-begin])) (define-syntax (-#%module-begin stx) (syntax-case stx () [(_ lng . rest) (with-syntax ([#%module-begin (datum->syntax #f '#%module-begin)]) ;; put the code in a module form, and fully expand that module (define mod-stx (local-expand #'(module ignored lng (#%module-begin . rest)) 'top-level (list))) ;; pattern-match on the #%plain-module-begin form to insert a require (syntax-case mod-stx (module #%plain-module-begin) [(module _ lng (#%plain-module-begin . mod-body)) #'(#%plain-module-begin (#%require lng) . mod-body)]))]))) ;; Yay the check syntax arrows work! (module foo (submod ".." mylang) typed/racket/base (ann (+ 1) Number)) (require 'foo) 

And if you want to somehow transform the body, you can do it before or after expansion.

Pattern matching for inserting extra (#%require lng) necessary because expanding the body of the module in the context where lng available is not enough. Returning the mod-body code from the module form means that the bindings will reference lng , but lng will not be available at run time. This is why I get a require: namespace mismatch; reference to a module that is not available error require: namespace mismatch; reference to a module that is not available require: namespace mismatch; reference to a module that is not available without it, and therefore it must be added after expansion.

Update from comments

However, as GeorgesDupéron noted in a comment, this is another problem. If lng provides the identifier x , and the module in which it is used imports another x , there will be an import conflict where it should not be. Require strings to be in a "nested area" relative to the module language so that they can shadow identifiers such as x here.

@ GeorgesDupéron found a solution to this problem in this email in the racket user list using (make-syntax-introducer) in mod-body to create a nested area.

 (module mylang racket (provide (rename-out [-#%module-begin #%module-begin])) (define-syntax (-#%module-begin stx) (syntax-case stx () [(_ lng . rest) (with-syntax ([#%module-begin (datum->syntax #f '#%module-begin)]) ;; put the code in a module form, and fully expand that module (define mod-stx (local-expand #'(module ignored lng (#%module-begin . rest)) 'top-level (list))) ;; pattern-match on the #%plain-module-begin form to insert a require (syntax-case mod-stx (module #%plain-module-begin) [(module _ lng (#%plain-module-begin . mod-body)) #`(#%plain-module-begin (#%require lng) . #,((make-syntax-introducer) #'mod-body))]))]))) 
+4
source share

All Articles