[racket] Evaluating code written in non-SEXP language

From: Dmitry Pavlov (dpavlov at ipa.nw.ru)
Date: Fri Sep 13 09:21:09 EDT 2013

Matthew,


 > You have a reader for your language that is triggered by "#lang",
> right?
>
> Is that reader in a `reader` submodule, or is it in a separate
> ".../lang/reader.rkt" module file?

In a separate file:

$ cat lang/reader.rkt
(module reader syntax/module-reader
   #:language 'slon/slon-language
   #:read slon-read
   #:read-syntax slon-read-syntax
   #:whole-body-readers? #t
   #:language-info '#(slon/lang/lang-info get-info #f)
   #:info (lambda (key defval default)
            (case key
              ((color-lexer)
               (dynamic-require 'slon/tool/syntax-color 'get-syntax-token))
              (else (default key defval))))
   (require slon/slon-parser))


I believe I used that approach before submodules appeared
in Racket... why does it matter anyway?

Regards,

Dmitry



>
> At Fri, 13 Sep 2013 17:01:36 +0400, Dmitry Pavlov wrote:
>> Matthew,
>>
>> Many thanks! Your code works perfectly for my program.
>>
>> Now I would like to describe my tries to make a standalone
>> executable out of my program.
>>
>> Most obvious way fails:
>>
>> $ raco exe slon-main.rkt
>> $ ./slon-main
>> standard-module-name-resolver: collection not found
>>     collection: "slon"
>>     in collection directories:
>>     context...:
>>      standard-module-name-resolver
>>
>> Not surprising, as the "slon" collection is not mentioned
>> in the slon-main.rkt itself, but is required dynamically.
>>
>> So my second thought was that maybe ++lib would help.
>> It did not. (Did I use the flag incorrectly?)
>>
>> $ raco exe ++lib slon/slon-language slon-main.rkt
>> $ ./slon-main
>> <same error message>
>>
>>
>> Seeking for the solution, I upgraded from raco to the Racket level:
>>
>> #lang racket
>> (require compiler/embed)
>>
>> (create-embedding-executable "slon-main"
>>        #:modules '((#f slon/slon-main) (#f slon/slon-language))
>>        #:literal-expression
>>          (parameterize ([current-namespace (make-base-namespace)])
>>                  (compile `(namespace-require 'slon/slon-main)))
>>        #:configure-via-first-module? #t
>>        #:verbose? #t)
>>
>> That worked great. "slon-main" was able to run independently
>> of its own location in the file system, and indenendeltly
>> of the presence of the original files in the slon/ collection
>> on disk.
>>
>> But there was another thing. In my implementation, I have
>> some binary data files that are loaded via
>>
>> (define-runtime-path eop.era "../eop/eop.era")
>>
>> The resulting program depended on those files. When I renamed
>> one of them, I got an error message:
>>
>> $ ./slon-main
>> with-input-from-file: cannot open input file
>>     path: /home/dpavlov/era/slon/../eop/eop.era
>>
>>
>> "OK", I thought, "raco distribute is supposed to fix that".
>>
>>
>> $ raco distribute slon-distr slon-main
>> $ ls ./slon-distr/lib/plt/slon-main/exts/ert/home/dpavlov/era/eop/
>> eop.era
>> $ ./slon-distr/bin/slon-main
>> standard-module-name-resolver: collection not found
>>     collection: "slon"
>>     in collection directories:
>>      /home/dpavlov/.racket/5.3.4/collects
>>      /home/dpavlov/era/slon/slon-distr/lib/plt/slon-main/collects
>>
>> The executable "slon-main" produced by raco distrubute is
>> different from the original one (although the size is the same).
>> I guess, raco distribute have eliminated the dependency of the
>> binary files with absolute paths, but it reintroduced the
>> dependency on the "slon" collection, which I previously got
>> rid of with the help of (create-embedding-executable)!
>>
>> And here I am stuck, asking for help.
>>
>>
>> Regards,
>>
>> Dmitry
>>
>>
>>
>>
>> On 09/13/2013 05:33 AM, Matthew Flatt wrote:
>>> I'm not sure I understand what you want, but here are some ideas about
>>> evaluating a text that would a module if only a "#lang" line were
>>> added.
>>>
>>> To start, here's a function to `require` an input port that contains a
>>> module's source. It uses the current namespace, and it gensyms a name
>>> for the module if you don't provide one. The part that I think is least
>>> obvious is using `current-module-declare-name` to set the name of the
>>> module to that it can be found by `dynamic-require`:
>>>
>>>    (require syntax/modread)
>>>
>>>    (define (require-input-port p [name (gensym)])
>>>      (define module-name (make-resolved-module-path name))
>>>      (parameterize ([current-module-declare-name module-name])
>>>        (eval-syntax (check-module-form ; ensures that `module` is bound
>>>                      (with-module-reading-parameterization
>>>                       (lambda ()
>>>                         (read-syntax (object-name p) p)))
>>>                      'ignored
>>>                      #f)))
>>>      (dynamic-require module-name #f))
>>>
>>> The `require-input-port` function assumes that the source starts with
>>> "#lang". You could use `input-port-append`, as others have suggested,
>>> to add a "#lang" line:
>>>
>>>    (define p (input-port-append #t
>>>                                 (open-input-string "#lang racket/base\n")
>>>                                 (open-input-file "body.rktd")))
>>>    (port-count-lines! p)
>>>    (require-input-port p)
>>>
>>> A problem with `input-port-append` is that line numbers are off by one
>>> for error reporting, and positions are off by the length of the first
>>> line. That's an annoyingly difficult problem to fix, but
>>> `prefix-input-port` below is my attempt (and maybe `input-port-append`
>>> should just work better along similar lines).
>>>
>>>    (define p (prefix-input-port #"#lang racket/base\n"
>>>                                 (open-input-file "body.rktd")))
>>>    (port-count-lines! p)
>>>    (require-input-port p)
>>>
>>> ----------------------------------------
>>>
>>> ;; prefix-input-port : bytes input-port -> input-port
>>> ;;  Directs position requests to the given port after the
>>> ;;  prefix is read.
>>> ;;  Closes the given input port when the result port is closed.
>>> (define (prefix-input-port prefix base-p)
>>>     (define-values (prefix-i prefix-o) (make-pipe))
>>>     (write-bytes prefix prefix-o)
>>>     (close-output-port prefix-o)
>>>     (define (prefix-done?)
>>>       (zero? (pipe-content-length prefix-i)))
>>>
>>>     (make-input-port
>>>      (object-name base-p)
>>>      ;; read
>>>      (lambda (bstr)
>>>        (define n (read-bytes-avail!* bstr
>>>                                      (if (prefix-done?)
>>>                                          base-p
>>>                                          prefix-i)))
>>>        (if (equal? n 0)
>>>            (wrap-evt base-p (lambda (v) 0))
>>>            n))
>>>      ;; peek
>>>      (lambda (bstr offset evt)
>>>        (define pre-n (pipe-content-length prefix-i))
>>>        (define n (if (offset . >= . pre-n)
>>>                      (peek-bytes-avail!* bstr
>>>                                          (- offset pre-n)
>>>                                          #f
>>>                                          base-p)
>>>                      (peek-bytes-avail!* bstr
>>>                                          offset
>>>                                          #f
>>>                                          prefix-i)))
>>>        (if (equal? n 0)
>>>            (wrap-evt base-p (lambda (v) 0))
>>>            n))
>>>      ;; close
>>>      (lambda ()
>>>        (close-input-port base-p))
>>>      ;; get-progress-evt
>>>      ;;  Difficult (impossible?) to support at the
>>>      ;;  prefix--base boundary.
>>>      #f
>>>      ;; commit
>>>      #f
>>>      ;; get-location
>>>      (lambda ()
>>>        (if (prefix-done?)
>>>            (port-next-location base-p)
>>>            (port-next-location prefix-i)))
>>>      ;; count-lines!
>>>      (lambda ()
>>>        (port-count-lines! prefix-i)
>>>        (port-count-lines! base-p))))
>>>
>>>
>>>
>
>


Posted on the users mailing list.