Evaluating file at runtime in Racket

51 views Asked by At

I am making an application using Racket in which Racket is also used as a scripting language. I want users to write scripts utilizing some structs and procedures provided in my executable.

For example I have userruntime.rkt:

(struct result (x y z))
(provide (all-from-out racket/base))
(provide (struct-out result))

And user writes script.rkt:

(define (my-even? x)
  (if (eq? x 0) #t
                (not (my-odd? (sub1 x)))))

(define (my-odd? x)
  (not (my-even? (sub1 x))))

(result (my-odd? 1) (my-odd? 2) (my-odd? 3))

I want to evaluate to the following result (you get the idea):

'(#<void> #<void> (result #t #f #t))

It seems that make-module-evaluator in racket/sandbox satisfies my needs. However I failed to find a way to pass the module (as a file compiled and linked in the final executable). The official documentation gives the following example:

(define base-module-eval
    (make-evaluator 'racket/base '(define (f) later)
                                 '(define later 5)))

Which is not requiring a file. When I try:

(make-module-evaluator "userruntime.rkt")  ; make-module-evaluator: expecting a `module' program; got userruntime.rkt [,bt for context]
;; or
(make-module-evaluator 'userruntime)  ; make-module-evaluator: expecting a `module' program; got userruntime[,bt for context]

How do I refer to the module in the file correctly? Thanks in advance.

1

There are 1 answers

2
Shawn On

If the module-decl arguments to make-module-evaluator are strings, they're treated as the code to evaluate. You have to pass a path to have it treated as a file to read from (Or an open input port ( Personally, I find make-evaluator easier to work with, but it has the same requirements for its input-program arguments.)

Also, your script.rkt has an infinite loop in its function definitions, you shouldn't use eq? to compare numbers, and the evaluated code doesn't have its toplevel values printed out the way the REPL does - better to wrap the code in a function and call that.

With this userruntime.rkt:

#lang racket/base
(provide (struct-out result))
(struct result (x y z) #:transparent)

and this fixed script.rkt:

(define (my-even? x)
  (if (= x 0)
      #t
      (my-odd? (sub1 x))))

(define (my-odd? x)
  (if (= x 0)
      #f
      (my-even? (sub1 x))))

(define (callback)
  (result (my-odd? 1) (my-odd? 2) (my-odd? 3)))

this code works:

(define evaluator
  (make-evaluator 'racket/base (string->path "script.rkt")
                  #:requires '("userruntime.rkt")))
(println (evaluator '(callback))) ; (result #t #f #t)

Since make-module-evaluator requires a module to load, not arbitrary code, one way to use it would be to change script.rkt to:

#lang racket/base
(require "userruntime.rkt")
(define (my-even? x)
  (if (= x 0)
      #t
      (my-odd? (sub1 x))))

(define (my-odd? x)
  (if (= x 0)
      #f
      (my-even? (sub1 x))))

(define (callback)
  (result (my-odd? 1) (my-odd? 2) (my-odd? 3)))

after which

(define evaluator
  (make-module-evaluator (string->path "script.rkt")
                         #:allow-for-require '("userruntime.rkt")))
(println (evaluator '(callback)))

will work.