Evaluating to get the output with a specific lang

54 views
Skip to first unread message

Christopher Lemmer Webber

unread,
Nov 9, 2019, 9:54:30 AM11/9/19
to Racket Users
(Caveat: I know the sandbox evaluator exists. I'm trying to understand
how to do this without it, to understand the evaluation machinery for
something.)

Let's say I write "#lang foo". For whatever reason, I have programs
that are coming in from users that are not necessarily being saved
to a file on disk... they may be coming from a GUI, read over the
network, etc etc. The only important thing is that at the end of the
program, the last expression returns some value, and I want access to
that value. Simplest example, let's say we have the following program

```
#lang foo

(define bar 1)

(+ bar 2)
```

I'd like to read this in and evaluate it, so presumably I'd want to get
3 returned.

I've tried to figure out how to do this from trivial examples but I'm
not having success. I can see that I can read in a module:

```
racket-sandbox.rkt> (parameterize ([read-accept-reader #t])
(call-with-input-string "#lang racket/base
(+ 1 2)"
(lambda (ip)
(read-syntax 'foo ip))))
#<syntax:foo::7 (module anonymous-module racket/base (#%module-begin (+ 1 2)))>
```

Cool, ok.

But when I pass this module-wrapped structure to eval, the result is void.

What should I do? Help appreciated!

Jay McCarthy

unread,
Nov 9, 2019, 10:03:59 AM11/9/19
to Christopher Lemmer Webber, Racket Users
Modules don't evaluate to values. They have effects and they have
exported symbols. If you want to observe the evaluation of your
language's module, you'll have to look at one of those two things.
Both are used by existing Racket languages and infrastructure: `raco
test` relies on test modules making effects on a global box that
counts how many tests ran and failed. `scribble` relies on inspecting
an export named `doc`. In either case, I think you want to make
`#%module-begin` capture the last expression and expose its value via
an effect or an export.

Jay

--
Jay McCarthy
Associate Professor @ CS @ UMass Lowell
http://jeapostrophe.github.io
Vincit qui se vincit.
> --
> You received this message because you are subscribed to the Google Groups "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/8736exhzh8.fsf%40dustycloud.org.

Christopher Lemmer Webber

unread,
Nov 10, 2019, 9:45:33 AM11/10/19
to Jay McCarthy, Racket Users
Thanks Jay for the helpful response as usual Jay; I really do appreciate
it.

It sounds like what I want is the case of the export. I think I can
figure out how to modify the #%module-begin to do that.

I guess a question remaining then is: if I'm doing this kind of dynamic
import of the module, is there a way to require from it (especially if
it isn't assigned to a "filename" on disk?). It appears there must be;
when I look at `build-program` in sandbox.rkt it also looks like it's
wrapping things in a module structure... but I don't see how it then
exports from that module or how the code evaluating it imports its
export. Or does it actually do so via an effect? I see there are some
channels involved (input-ch / result-ch) so maybe it's passing back the
result through there, but I am having trouble figuring out how.

Having said all that, maybe I can envision a way to make it work:

- Have a parameter that, when parameterized, sets up the expected
channel that a module, upon being loaded, is expected to return its
"result" from.
- When evaluating the module, parameterize that channel; read from
it after evaluating and extract the value.

Does that seem like the right approach? I guess I will give it a try,
anyway.

- Chris

Christopher Lemmer Webber

unread,
Nov 10, 2019, 1:08:55 PM11/10/19
to Jay McCarthy, Racket Users
Christopher Lemmer Webber writes:

> I guess a question remaining then is: if I'm doing this kind of dynamic
> import of the module, is there a way to require from it (especially if
> it isn't assigned to a "filename" on disk?). It appears there must be;
> when I look at `build-program` in sandbox.rkt it also looks like it's
> wrapping things in a module structure... but I don't see how it then
> exports from that module or how the code evaluating it imports its
> export. Or does it actually do so via an effect? I see there are some
> channels involved (input-ch / result-ch) so maybe it's passing back the
> result through there, but I am having trouble figuring out how.
>
> Having said all that, maybe I can envision a way to make it work:
>
> - Have a parameter that, when parameterized, sets up the expected
> channel that a module, upon being loaded, is expected to return its
> "result" from.
> - When evaluating the module, parameterize that channel; read from
> it after evaluating and extract the value.
>
> Does that seem like the right approach? I guess I will give it a try,
> anyway.

Well I gave it a try and couldn't quite figure out how to make it work.
I tried writing out this file, dungeon/room-ch.rkt:

```
#lang racket/base

(provide current-room-channel)

(define current-room-channel
(make-parameter #f))
```

Then I tried writing test-read-ch.rkt:

```
#lang racket

(require dungeon/room-ch)

(define test-prog
"#lang racket/base

(require dungeon/room-ch)

(channel-put (current-room-channel) 'foo)")

(define (try-reading)
(parameterize ([read-accept-reader #t]
[current-room-channel (make-channel)])
(eval (call-with-input-string test-prog read))
(channel-get (current-room-channel))))
```

It just hangs, so I assume that the module never wrote to that channel.
I guess it probably wouldn't until it's required, but I have no idea how
to "require" this dynamically-read-in module to prime it?

Christopher Lemmer Webber

unread,
Nov 10, 2019, 2:18:04 PM11/10/19
to Jay McCarthy, Racket Users
> Jay McCarthy writes:
>> Modules don't evaluate to values. They have effects and they have
>> exported symbols. If you want to observe the evaluation of your
>> language's module, you'll have to look at one of those two things.
>> Both are used by existing Racket languages and infrastructure: `raco
>> test` relies on test modules making effects on a global box that
>> counts how many tests ran and failed. `scribble` relies on inspecting
>> an export named `doc`. In either case, I think you want to make
>> `#%module-begin` capture the last expression and expose its value via
>> an effect or an export.

Christopher Lemmer Webber writes:

> Well I gave it a try and couldn't quite figure out how to make it work.
> I tried writing out this file, dungeon/room-ch.rkt:
>

[...]

> It just hangs, so I assume that the module never wrote to that channel.
> I guess it probably wouldn't until it's required, but I have no idea how
> to "require" this dynamically-read-in module to prime it?

Wow, ok. So I figured, "this must be possible to talk to a module
loaded at runtime, because I see in sandbox.rkt that when the user
specifies a lang to the sandbox that it makes a module":

(define (use-lang lang) `(module program ,lang . ,body))

So I figured that ok, there must be some way to evaluate a module and
get it to "run", and it must be taking either of the approaches Jay
mentioned. So I spent about an hour digging around in sandbox.rkt and
taking notes thinking I'd find out where it did that.

Well... I was in for a surprise. My assumptions appear to be wrong.
As far as I can tell:

- sandbox.rkt first sets up a fresh namespace to do the evaluation in
(ok, expected)
- Then later in evaluate-program:

(define ns
(syntax-case* program (module) literal-identifier=?
[(module mod . body)
(identifier? #'mod)
(let ([mod #'mod])
(lambda ()
(for ([submod-name (in-list submod-names)])
(eval `(when (module-declared? '(submod (quote ,mod) ,submod-name) #f)
(dynamic-require '(submod (quote ,mod) ,submod-name) #f))))
(module->namespace `(quote ,(syntax-e mod)))))]
[_else #f]))

sandbox.rkt is then piecing apart the module and taking the module
path and adding it to the namespace?! Or at least preparing to,
since this is wrapped in a lambda to be run later.

- It actually appears that the program (the whole `(module ...)`
structure is actually just eval'ed first, and *then* I guess the
namespace is set up:

(if (and (pair? program) (eq? 'begin (car program)))
(eval* (cdr program))
(eval program))
(when ns (set! ns (ns)))

- At the end of it the current-namespace is set to ns if indeed
evaluate-program is dealing with a (module ...) thing:

(when (namespace? ns) (current-namespace ns))

- Then later on when the user uses the sandboxed evaluator and passes
in expressions, I guess that namespace is used and evaluation takes
place in it.

That is NOT what I was expecting. What I figured when I saw the
`(module program ,lang . ,body) stuff was that the module would be
evaluated on some sort of very generic
module-dynamically-loaded-at-runtime way. That doesn't quite seem to be
the case.

This was a useful read; I'm still unsure if I'm reading it right
though. I guess it seems to say "this tooling doesn't quite exist out
of the box, but you can assemble something resembling it on your own"?

Eric Griffis

unread,
Nov 10, 2019, 3:04:18 PM11/10/19
to Christopher Lemmer Webber, Jay McCarthy, Racket Users

On Sun, Nov 10, 2019 at 6:45 AM Christopher Lemmer Webber <cwe...@dustycloud.org> wrote:
>
> It sounds like what I want is the case of the export.

I'll run with this.


> I guess a question remaining then is: if I'm doing this kind of dynamic
> import of the module, is there a way to require from it (especially if
> it isn't assigned to a "filename" on disk?).

Try `local-require` inside a `let` or `let-values` body.


> It appears there must be;
> when I look at `build-program` in sandbox.rkt it also looks like it's
> wrapping things in a module structure... but I don't see how it then
> exports from that module or how the code evaluating it imports its
> export.  Or does it actually do so via an effect?  I see there are some
> channels involved (input-ch / result-ch) so maybe it's passing back the
> result through there, but I am having trouble figuring out how.

Apologies for stating the obvious, but to get a value out of a module, we `provide` it:

In foo/main.rkt:

```
(define-syntax-rule (foo-module-begin form ... final-form)
  (#%module-begin (provide the-foo) form ... (define the-foo final-form)))
```

Then we can produce and consume foo-based modules like this:

```
(module bar-mod foo (define bar 1) (+ bar 2))

(define my-bar (let () (local-require 'bar-mod) the-foo))
```

Coincidentally, I've got notes on using this technique to wedge a `#lang` into the body of a defining form; a `define-foo` form, for example, that treats its contents as if it were read from a file that starts with `#lang foo`. It's part of an upcoming blog series on functional meta-programming with Racket. I've got a few graphics packages in the pipe to celebrate the Racket Game Jam, so it might be a few weeks before this article hits the blog, but I'm happy to continue discussing and share my notes any time.

Eric

Christopher Lemmer Webber

unread,
Nov 10, 2019, 3:19:06 PM11/10/19
to Eric Griffis, Jay McCarthy, Racket Users
Hi Eric! Thanks very much for the reply.

Eric Griffis writes:

>> It appears there must be; when I look at `build-program` in
>> sandbox.rkt it also looks like it's wrapping things in a module
>> structure... but I don't see how it then exports from that module or
>> how the code evaluating it imports its export. Or does it actually
>> do so via an effect? I see there are some channels involved
>> (input-ch / result-ch) so maybe it's passing back the result through
>> there, but I am having trouble figuring out how.
>
> Apologies for stating the obvious, but to get a value out of a module, we
> `provide` it:
>
> In foo/main.rkt:
>
> ```
> (define-syntax-rule (foo-module-begin form ... final-form)
> (#%module-begin (provide the-foo) form ... (define the-foo final-form)))
> ```
>
> Then we can produce and consume foo-based modules like this:
>
> ```
> (module bar-mod foo (define bar 1) (+ bar 2))
>
> (define my-bar (let () (local-require 'bar-mod) the-foo))
> ```

This approach works, but I'm not sure it works for my use case, because
`module` is appearing at compile-time.

Let me try to lay out the use case, and maybe it helps.

- We are in a MUD, or some other interactive game. The game is running
live. Some players have capabilities to provide new code for game
characters in the system or other interesting features.

- The users are able to write scripts that return things... however,
they are providing the scripts as the game is running. (This will be
ocap safe, but I'll explains how that works in a separate
email... just trust me that I believe that if I can run it with in a
special restricted language and return a value from that module which
is loaded *at runtime* somehow, I can pull off this goal safely.) As
such, we cannot at the time that we are starting the program actually
know or load all of the modules, because the users are providing some
of them live.

- As such we need to be able to do something along the lines of what
you just did, but we have to do it at runtime.

- I don't necessarily want to attach each of these user-supplied
modules to a global namespace; a user might experiment with making
many of them, and there's no need.

Does that make sense?

I'll explain more about how this can be ocap safe in a future email.
For the moment, if you're curious on how this design works, more is
here (the "emaker" pattern):

http://www.skyhunter.com/marcs/ewalnut.html#SEC16

Christopher Lemmer Webber

unread,
Nov 10, 2019, 4:44:53 PM11/10/19
to Eric Griffis, Jay McCarthy, Racket Users
Well, I think I figured out how to get further:

with example1.rkt being:

```
#lang racket/base
;; #lang dungeon/misery

(define ((make-start-game read-save-file) player-name)
(list 'running-this-read-save-file: read-save-file
'on-player-name: player-name
'result: (read-save-file player-name)))

(define (entrypoint read-save-file)
(make-keyword-procedure
(lambda (kws kw-args method-name . args)
(define method
(case method-name
['start-game (make-start-game read-save-file)]
['what-is-make-start-game (lambda () make-start-game)]
['throw-error
(lambda ()
(error "oh no :("))]
[else (error "owch!")]))
(keyword-apply method kws kw-args args))))

(provide entrypoint)
```

It seems I'm able to read things in:

```
read-example.rkt> (parameterize ([current-namespace
(module->namespace 'racket/base)]
[read-accept-reader #t])
(define stx
(call-with-input-file "example1.rkt"
(lambda (ip)
(read-syntax 'my-module ip))))
(eval stx)
(eval '(require 'example1))
(dynamic-require ''example1 'entrypoint))
#<procedure:entrypoint>
```

I know that's kludgy, but it seems closer to being on track. Thanks to
everyone who has responded.

Eric Griffis

unread,
Nov 10, 2019, 6:56:59 PM11/10/19
to Christopher Lemmer Webber, Racket Users
This works:

1. mkdir foo; cd foo; raco pkg install

2. create foo/main.rkt:

```
#lang racket/base

(module reader racket/base
  (require racket/port)
  (provide (rename-out [foo-read read]
                       [foo-read-syntax read-syntax]))
  (define (foo-read port)
    `(module ,(gensym 'foo) racket/base
       (provide the-foo)
       (define the-foo (let () ,@(port->list read port)))))
  (define (foo-read-syntax path port)
    (datum->syntax #f (foo-read port))))
```

3. In the REPL or another file, we can simulate the game's interaction with the player's connection through an input port:

```
(define foo-source #<<END
#lang foo


(define bar 1)

(+ bar 2)
END
  )

(define player-input (open-input-string foo-source))

(define source
  (let ([define-mod (parameterize ([read-accept-reader #t])
                      (read player-input))])
    (define mod-name (cadr define-mod))
    `(begin ,define-mod (let () (local-require (quote ,mod-name)) the-foo))))

(println `(GOT ,(eval source (module->namespace 'racket/base))))
```

The value-extraction code is kind of messy, so you might want to stow it in a `port-read-handler`, like this:

```
(port-read-handler
 player-input
 (let ([default-handler (port-read-handler player-input)])
   (case-lambda
     [(in) (define original-handler (port-read-handler in))
           (define define-mod

             (parameterize ([read-accept-reader #t])
               (port-read-handler in default-handler)
               (begin0 (read in) (port-read-handler in original-handler))))
           (define mod-name `(quote ,(cadr define-mod)))
           (eval `(begin ,define-mod (let () (local-require ,mod-name) the-foo))
                 (module->namespace 'racket/base))]
     [(in source) (datum->syntax #f (read in))])))

(println `(GOT ,(read player-input)))
```

Eric

Christopher Lemmer Webber

unread,
Nov 10, 2019, 7:15:25 PM11/10/19
to Eric Griffis, Racket Users
Hey Eric! Thanks, I'll try to soak in this a bit tomorrow. :)

Jay McCarthy

unread,
Nov 10, 2019, 7:58:15 PM11/10/19
to Christopher Lemmer Webber, Eric Griffis, Racket Users
I feel like I might not understand what you want, but it feels like
you just want to use `make-module-evaluator` from `racket/sandbox`:

```
#lang racket/base
(require racket/sandbox)

(define (read-script s)
(((make-module-evaluator s) 'script) 5))

(module+ test
(read-script
"#lang racket/base
(provide script)
(define (script x) (add1 x))")
(read-script
"#lang typed/racket/base
(provide script)
(: script (-> Number Number))
(define (script x) (add1 x))"))
```

You may want to set the `#:language` argument to control the language.
And you will also want to set `sandbox-namespace-specs` to link up
your internal data-structure providing modules so you can communicate.
But other than those two things, it should be pretty straightforward.
What is different about this than what you are trying to do?

Jay

--
Jay McCarthy
Associate Professor @ CS @ UMass Lowell
http://jeapostrophe.github.io
Vincit qui se vincit.

On Sun, Nov 10, 2019 at 7:15 PM Christopher Lemmer Webber
> --
> You received this message because you are subscribed to the Google Groups "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/87r22fgteu.fsf%40dustycloud.org.

David Storrs

unread,
Nov 11, 2019, 9:00:46 AM11/11/19
to Christopher Lemmer Webber, Eric Griffis, Jay McCarthy, Racket Users
Don't forget to handle multiple value return. What will you do if the last line in the file is (values a b)?

Also, side effects. Do you care if someone includes a print/write/display statement? You mentioned that the code would be safe, so presumably tcp-connect and sqlite3-connect are not issues.

--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.

Christopher Lemmer Webber

unread,
Nov 11, 2019, 11:25:16 AM11/11/19
to Jay McCarthy, Eric Griffis, Racket Users
Jay McCarthy writes:

> I feel like I might not understand what you want, but it feels like
> you just want to use `make-module-evaluator` from `racket/sandbox`:
>
> ```
> #lang racket/base
> (require racket/sandbox)
>
> (define (read-script s)
> (((make-module-evaluator s) 'script) 5))
>
> (module+ test
> (read-script
> "#lang racket/base
> (provide script)
> (define (script x) (add1 x))")
> (read-script
> "#lang typed/racket/base
> (provide script)
> (: script (-> Number Number))
> (define (script x) (add1 x))"))
> ```
>
> You may want to set the `#:language` argument to control the language.
> And you will also want to set `sandbox-namespace-specs` to link up
> your internal data-structure providing modules so you can communicate.
> But other than those two things, it should be pretty straightforward.
> What is different about this than what you are trying to do?
>
> Jay

I think you're right, the sandbox code is fine enough to use. I was
trying to remember why I hadn't been exploring this method, but quickly
rediscovered it:

racket-sandbox.rkt> (define evalu8
(make-evaluator 'racket/base))

; current-load-relative-directory: `exists' access denied for /home/cwebber/sandbox/
; Context:
; /gnu/store/8iy4dl15yl016vgkd040njzf9351hh7j-racket-7.3/share/racket/pkgs/sandbox-lib/racket/sandbox.rkt:716:0 evaluate-program
; /gnu/store/8iy4dl15yl016vgkd040njzf9351hh7j-racket-7.3/share/racket/pkgs/sandbox-lib/racket/sandbox.rkt:853:2 user-process

Looks like this is because I'm running Racket on Guix; the other source
I can find of this coming up is
https://users.racket-lang.narkive.com/A6CLMoNo/question-about-sandbox-rktl-failing-its-unit-test
and it would indeed make sense if it *was* related to symbolic links,
since Guix makes heavy use of symlinks for its functional package
management.

Looks like the problem is security guards really:

racket-sandbox.rkt> (parameterize ([sandbox-security-guard (current-security-guard)])
(define evalu8
(make-evaluator 'racket/base))
(evalu8 '(+ 1 2 3)))
6

Anyway that's an unrelated bug, but probably one I should file.
Nonetheless I think even in the interim I have a way to move forward,
thanks!
Reply all
Reply to author
Forward
0 new messages