拍子模式:我可以在REPL的给定名称空间中评估单个表单吗?

时间:2019-04-06 04:59:14

标签: emacs racket read-eval-print-loop racket-mode

我正在Emacs中通过racket-mode在Racket REPL工作,在多个模块中编写代码。

是否有一种方法可以在其自身模块的上下文中从我当前不在的模块中执行单个表单?

例如:

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"))

如果我从racket-run呼叫web.rkt,我会得到提示,提示web.rkt>。如果然后运行(display-default-view),则会显示“ Hello world”。

如果我随后访问view.rkt并将默认文本定义更改为:

(define default-text "Hi")

并重新评估default-text的定义,它的评估结果很好,我的提示仍然是web.rkt>

当我在REPL中输入default-text时,我得到“ Hi”。但是当我运行(display-default-view)时,仍然会看到“ Hello world”。我想这是因为我所做的只是在default-text中定义了一个新的web.rkt

我希望 看到输出更改为“ Hi world” ---即view.rkt模块的行为将被更新。就像我看到default-text是否生活在web.rkt模块中一样。

在repl上动态地重新评估单个表单以更改程序行为的想法非常了不起,但是在这里似乎不太可行。

有没有一种方法可以像我在球拍模式中期望的那样进行操作?或者,如果不是,是否有一种仅运行模块而不运行它的机制,以便我可以自己构建一些东西来执行Enter-execute-exit舞蹈?

1 个答案:

答案 0 :(得分:2)

更新后的答案更简单

我们可以通过在REPL中输入该名称空间,评估这些表格,然后重新输入原始名称空间,来评估当前文件名称空间中REPL中的表单。最简单的方法似乎是用函数包装这些表单,以输入当前文件的名称空间(之前),然后重新输入原始名称空间(之后),然后将所有这些发送到现有的Racket模式代码中,以评估表单中的表单。 REPL。

我们可以通过以下方法来实现:构建一串包装好的命令,将其写入临时缓冲区,将整个缓冲区标记为我们的区域,然后将其发送到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)))))

这些功能大部分应该与版本无关,它们仅取决于racket-send-buffer(在以后的版本中似乎仍会保留)。


编辑1:(注意-这似乎不适用于较新版本的球拍模式。此功能自2018年4月1日起有效,但较新版本似乎已重构了一些内部结构几乎在所有情况下,上面的代码都是可取的。)

对不起,我相信我最初是对该问题有误解。看起来您的意思是直接从view.rkt执行命令,而无需手动更改REPL中的名称空间。我没有看到在球拍模式下有任何内置功能可以做到这一点,但是围绕此过程编写Elisp包装器并不难。 enter!中的以下导入,切换到当前缓冲区的文件的名称空间,在区域中发送代码,然后切换回原始名称空间。所使用的代码与racket-send-regionracket-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)))

请注意,如果您经常使用此功能,则此功能可能会使用更多的错误检查功能(例如,导入require/enter会掩盖enter!的所有先前定义)。

我还在下面保留了有关如何在REPL中手动切换名称空间的原始文本,以防万一。


您可以使用enter!模块中的函数racket/enter来切换名称空间,以修改另一个文件的名称空间中的定义。

在web.rkt中调用racket-run之后,您可以在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"

请参见Racket documentation for more details on interactive module loading