#:g1: スコープも含めて式をコピーするエディタコマンドが欲しい

Posted 2021-05-25 19:16:48 GMT

Lispのコードを編集している時に式を外側に持ち出す時に囲んでいるスコープの変数も一緒に連れて行きたいことがたまにありました。

(defun foo (&aux x)
  (list x x x x))

(list x x x x)をコピーする際に、

(let ((x x))
  (list x x x x))

のように式がコピーできたら良いなというところですが、実験で変数環境を展開する無意味なマクロを作成している時に、これを応用すれば環境ごとコピーできるエディタコマンドが作成できるのでは、と思ったので作成してみます。

とりあえず、変数環境を展開する無意味な無意味なマクロとはこのようなものです。

#+LispWorks
(defmacro bind-env (&body body &environment env)
  `(let (,@(mapcar (lambda (x)
                     (if (walker:variable-special-p x env)
                         `(,x ,x)
                         `(,x ,x)))
                   (walker::env-lexical-variables env)))
     '.bind-env.
     ,@body))

LispWorks用ですが、大体の処理系に対応するAPIは備わっていますので移植は簡単です。
こんな感じに使いますが、マクロを展開すると、bind-envの周りの変数をletのフォームとして組み立てます。

(let ((x 42))
  (bind-env
    (list x x)))
===>
(let ((x 42))
  (let ((x x)) 
    '.bind-env.
    (list x x)))

エディタコマンドの作成

上記の謎マクロによって環境をletの式として表現できるようになったので、これをエディタのコマンドにしてみます。
LispWorksのHemlock用のコードですが、環境込みでマクロ展開した部分式をletで囲んだ文字列を作成するだけなので、SLIME等でも作成できると思います。

(defun toplevel-form-to-string (point)
  (let (str form-beg form-end)
    (with-defun-start-end-points (beg end :errorp nil) point
      (setq str (points-to-string beg end))
      (setq form-beg (copy-point beg))
      (setq form-end (copy-point end)))
    (values str form-beg form-end)))

(defun form-to-string (point) (let (str form-beg form-end) (save-excursion (with-point ((beg point)) (setq form-beg (copy-point beg)) (forward-form-command 1) (setq str (points-to-string beg (current-point))) (setq form-end (copy-point (current-point))))) (values str form-beg form-end)))

(defmacro bind-env (&body body &environment env) `(let (,@(mapcar (lambda (x) (if (walker:variable-special-p x env) `(,x ,x) `(,x ,x))) (walker::env-lexical-variables env))) '.bind-env. ,@body))

(defun extract-binds (form) (labels ((%extract-binds (form) (cond ((atom form) form) ((and (consp form) (eq 'let (elt form 0)) (equal ''.bind-env. (elt form 2))) (return-from extract-binds (elt form 1))) (T (%extract-binds (print (car form))) (%extract-binds (cdr form)))))) (%extract-binds form)))

(defcommand "Save Form With Env" (p) "Save Form With Env" "Save Form With Env" (declare (ignore p)) (multiple-value-bind (killed killed-beg killed-end) (form-to-string (current-point)) (with-point ((point (current-point))) (multiple-value-bind (whole whole-beg whole-end) (toplevel-form-to-string (current-point)) (declare (ignore whole)) (let* ((killed/env (concatenate 'string "(editor::bind-env " killed ")")) (whole/env (concatenate 'string (points-to-string whole-beg killed-beg) killed/env (points-to-string killed-end whole-end))) (expanded (with-compilation-environment-at-point (point) (walker:walk-form (read-from-string whole/env)))) (binds (format nil "~&(let ~A~% ~A)" (write-to-string (extract-binds expanded)) killed))) (set-current-cut-buffer-string (current-window) binds))))))

使ってみる

切り取り動作にするかコピー動作にするか迷いましたが、とりあえず今回はコピー動作にしてみました。

(defun fib (n)
  (if (< n 2)
      n
      (+ (fib (1- n))
         (fib (- n 2)))))

(ifの前でM-x Save Form With Envすると、

(let ((n n))
  (if (< n 2)
      n
      (+ (fib (1- n))
         (fib (- n 2)))))

がコピーされるので、適宜貼り付けることが可能です。
こんな感じの手作りインライン展開をする時などに便利なのではないでしょうか(便利か?)

(defun fib (n)
  (if (< n 2)
      n
      (+ (let ((n (1- n)))
           (if (< n 2)
               n
               (+ (fib (1- n))
                  (fib (- n 2)))))
         (let ((n (- n 2)))
           (if (< n 2)
               n
               (+ (fib (1- n))
                  (fib (- n 2))))))))

まとめ

今回は、letに抜き出しましたが、defunの式をlambdaに抜き出したりしても良さそうですね。


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus