3
votes

I'm working at the Racket REPL via racket-mode in Emacs, writing code in multiple modules.

Is there a way to execute a single form from a module I'm not currently 'in', in the context of its own module?

For instance:

web.rkt

#lang racket
(require "view.rkt")

(define (display-default-view)
  (display (default-view)))

view.rkt

#lang racket

(provide default-view)

(define default-text "Hello")

(define (default-view)
  (string-append default-text " world"))

If I call racket-run from web.rkt I get a prompt saying web.rkt>. If I then run (display-default-view) I get "Hello world".

If I then visit view.rkt and change the default-text definition to:

(define default-text "Hi")

and re-evaluate the default-text definition, it evaluates fine, and my prompt still says web.rkt>.

When I enter default-text at the REPL I get "Hi". But when I run (display-default-view) I still get "Hello world". I'm presuming this is because all I've done is define a new default-text in web.rkt.

I'd expect to see output change to "Hi world" --- i.e. the behaviour of the view.rkt module to be updated. Just like I'd see if default-text lived in the web.rkt module.

The idea of dynamically re-evaluating single forms at the repl to change program behaviour is terrific, but it seems to not quite work here.

Is there a way to get this behaving as I would expect in racket-mode? Or if not, a mechanism to just enter a module, without running it, so that I can build something myself to do an enter-execute-exit dance?

1
I've updated my answer. Originally I thought you were asking how to change a variable in a different namespace in the REPL, but upon re-reading your question, it seems that you would like to evaluate forms in their file's namespace while in that file's buffer. I hope the edit better answers your question (and let me know if I'm still misunderstanding it).D. Gillis

1 Answers

2
votes

Updated, simpler answer:

We can evaluate forms in the REPL in the current file's namespace by entering that namespace in the REPL, evaluating these forms, and then re-entering our original namespace. The easiest way to do this seems to be wrapping these forms with functions to enter the current file's namespace (before) and re-entering the original namespace (after) and then sending all of this into the existing Racket-mode code for evaluating forms in the REPL.

We can do this by building a string of our wrapped commands, writing it to a temporary buffer, marking the whole buffer as our region, and then sending it to racket-send-region.

(defun my-racket-current-namespace-wrapped-commands (buffer-file-string commands)
  "generate string containing commands wrapped with Racket functions to enter
   the current-namespace and then exit it upon finishing"
  (concat "(require (only-in racket/enter enter!))"
          "(enter! (file "
          buffer-file-string
          "))"
          commands
          "(enter! #f)"))

(defun my-racket--send-wrapped-current-namespace (commands)
  "sends wrapped form of commands to racket-send-region function via a temporary buffer"
  (let ((buffer-file-string (prin1-to-string buffer-file-name)))
    (with-temp-buffer
      (insert
       (my-racket-current-namespace-wrapped-commands buffer-file-string commands))
      (mark-whole-buffer)
      (racket-send-region (point-min) (point-max)))))

(defun my-racket-send-region-current-namespace (start end)
  "send region to REPL in current namespace"
  (interactive "r")
  (unless (region-active-p)
    (user-error "No region"))
  (let ((commands (buffer-substring (region-beginning) (region-end))))
    (my-racket--send-wrapped-current-namespace commands)))

(defun my-racket-send-last-sexp-current-namespace ()
  "send last sexp to REPL in current namespace"
  (interactive)
  (let ((commands (buffer-substring (my-racket--repl-last-sexp-start)
                                    (point))))
    (my-racket--send-wrapped-current-namespace commands)))

(defun my-racket--repl-last-sexp-start ()
    "get start point of last-sexp
     permanent (and slightly simplified) copy of racket mode's last-sexp-start private function"
  (save-excursion
    (progn
      (backward-sexp)
      (if (save-match-data (looking-at "#;"))
          (+ (point) 2)
        (point)))))

These functions should mostly be version agnostic - they only depend on racket-send-buffer (which seems likely to remain in future versions).


Edit 1: (Note - this does not seem to work as is for newer versions of Racket-mode. This worked as of the April 01, 2018 release, but newer versions seem to have refactored some of the internals this relied on. In almost all cases, the code above is preferable.)

Sorry, I believe that I originally misunderstood the question. It looks like you mean executing the command straight from view.rkt without having to manually change the namespace in the REPL. I didn't see any built-in functionally in racket-mode that does this, but it's not too hard to write an Elisp wrapper around this process. The following imports in enter!, switches to the current buffer's file's namespace, sends the code in the region, and then switches back to the original namespace. The code used is very similar to what racket-mode uses for racket-send-region and racket-send-last-sexp.

(defun my-racket-send-region-current-namespace (start end)
  "Send the current region to the Racket REPL as that namespace"
  (interactive "r")
  (when (and start end)
    (racket-repl t)
    (racket--repl-forget-errors)
    (let ((proc (racket--get-repl-buffer-process)))
      (with-racket-repl-buffer
        (save-excursion
          (goto-char (process-mark proc))
          (insert ?\n)
          (set-marker (process-mark proc) (point))))
      (comint-send-string proc "(require (only-in racket/enter enter!))")
      (comint-send-string proc
                          (concat "(enter! (file "
                                  (prin1-to-string buffer-file-name)
                                  "))"))
      (comint-send-string proc "\n"))
    (racket--repl-show-and-move-to-end)
    (racket--send-region-to-repl start end)
    (let ((proc (racket--get-repl-buffer-process)))
      (with-racket-repl-buffer
        (save-excursion
          (goto-char (process-mark proc))
          (insert ?\n)
          (set-marker (process-mark proc) (point))))
      (comint-send-string proc "(enter! #f)")
      (comint-send-string proc "\n"))))


(defun my-racket-send-last-sexp-current-namespace ()
  (interactive)
  (my-racket-send-region-current-namespace
   (save-excursion
     (backward-sexp)
     (if (save-match-data (looking-at "#;"))
         (+ (point) 2)
       (point)))
   (point)))

Note that if you're using this frequently, this function could probably use more error checking (e.g. the import of require/enter will clobber any previous definition of enter!).

I've also kept the original text below about how to manually switch namespaces in the REPL, in case it helps.


You can use the function enter! in the racket/enter module to switch namespaces to modify definitions in the namespace of the other file.

After calling racket-run in web.rkt, you could do the following in the REPL:

(display-default-view) ;; output is "Hello world"
(require racket/enter)
(enter! "view.rkt")    ;; change namespace to view.rkt
(define default-text "Hi")
(enter! #f)            ;; return to original namespace
(display-default-view) ;; output is "Hi world"

See the Racket documentation for more details on interactive module loading.