秀丸エディタ用、Emacs の eval-print-last-sexp 風マクロ for Scala, Clojure, Gauche, Groovy, Python, Ruby, JScript, Haskell and Perl
Emacs の lisp-interaction-mode に、編集中のテキスト上のカーソル直前位置の Emacs Lisp の S 式を評価する便利なコマンド “eval-print-last-sexp” がありますが、その秀丸エディタ用、各種プログラミング言語対応版です。下記は HTML で文章を書きながら、その中の Clojure のサンプルコードを評価している例です。矢印位置にカーソル (|) がある状態で、この場合は “eval-clojure.mac ” を実行してみます。
<p>下記は、クイックソートのサンプルです:
<pre>
(defn qsort [[x & xs]]
(when x
(let [smaller #(< % x)]
(lazy-cat
(qsort (filter smaller xs))
[x]
(qsort (remove smaller xs)) ))))| ←
</pre>
定義が評価されてシンボルが返ります。
<p>下記は、クイックソートのサンプルです:
<pre>
(defn qsort [[x & xs]]
(when x
(let [smaller #(< % x)]
(lazy-cat
(qsort (filter smaller xs))
[x]
(qsort (remove smaller xs)) ))))
#'user/qsort ←
</pre>
そのまま試しに実行してみます。
(qsort '(5 3 8 9 2))| ←
その場に結果が返ります。
(qsort '(5 3 8 9 2)) (2 3 5 8 9) ←
いずれも、日本語も通ります。下記は Scala の例です。
val l = "日本語" :: "テキスト" :: Nil l: List[java.lang.String] = List(日本語, テキスト)
どこか、昔の Basic インタプリタで開発をしていたような快適さがあります (あいにく SmallTalk の経験は無い)。とりわけ、関数型言語と相性が良いんでないかしら、このスタイルは。メリットとしては、以下のような感じかと思います:
- 言語をその場でチャンポンに書ける: ガッツリとアプリを開発するのであれば Emacs なり Eclipse なりの各言語のモードを利用すれば良いのでしょうが、つらつらと考えながら各言語を横断的に、サンプルなどのスクリプト片を次々に試しながらコメントも書きたい場合などには、同一テキスト上でその場で評価し、すぐに結果が返る方が快適です
- コピペをしなくてすむ: いちいちエディタ上で書いて、それを REPL にコピペ実行、というのは、思考を阻害します
- 起動オーバーヘッドが無い: REPL のセッションをバックグラウンドで維持しますので、とりわけ Scala や Clojure などの、JVM を使う、起動が異様に遅い REPL でも、2 度目以降の評価はサクサクです。非力なノートマシンには、これがありがたい
- エディタから離れなくて済む: 一日のほとんどをエディタ上で過ごす身としては、ひととおりのスクリプト実行がその場でできるというのはありがたいです。コード片も、無くさず記録として残りますし
- キー一発: キーを割り当てておけば、普段書きのテキストから各言語にキー一発 (2 ストロークならば二発ですが) でアクセスできます
秀丸エディタは、ver .8.0 以降のマクロで COM オブジェクトの操作をサポートしたため、以前にはできなかった複雑なアプリケーション連携や、テキストを開いている間だけ永続するオブジェクト等を持つことができるようになりました。今回のマクロも、バックグラウンドで各言語の REPL セッションを起動して、テキストをクローズするまでの間、状態を維持することができるようになったことで実現できました。
詳細は以下ですが、詳しくはコードを読んでください。なお、範囲選択をして実行すれば、選択範囲を評価します。
| カーソル以前のどこまでを一単位として評価? | 入出力方式 | 既知の問題 | |
|---|---|---|---|
| カーソルのある行、あるいは対応する括弧かヒアドキュメントによって継続している複数行 (Scala に “\” による行継続は無い) | 入力は REPL の :load、出力は STDOUT (SJIS) | あいかわらず初回起動が遅いが、こればかりはどうしようもない | |
| カーソル直前の S 式 | 入力は専用 REPL、出力は STDOUT (SJIS) | 初回起動は、そこそこに遅い。STDERR をファイルに出力する方法が分からなかったが、多分あまり支障はない | |
| カーソル直前の S 式 | 入出力とも専用 REPL (UTF-8) | 特に無い。Scheme は Gauche がお気に入りなので、Guile は確認していない。 | |
| カーソルのある行、あるいは対応する括弧かヒアドキュメントによって継続している複数行、あるいは “\” で継続している複数行 | 入力は REPL の \l、出力は STDOUT (SJIS) | jline が “unix” モードでないと STDIN を正しく扱えなかったため、動作に MinGW の sh(1) と stty(1) が必要 | |
| 第一カラムが空白ではないカーソル行、あるいはカーソル行以前で第一カラムが空白ではない行までの複数行、あるいは “\” で行継続をしている複数行 | 入力は専用の REPL、出力は STDOUT (UTF-8) | REPL の返事が淡白すぎて、返り値が分かりづらい | |
| カーソルのある行、あるいはカーソル行上の “end” とカラムが合う直前の開始行までの複数行、あるいは “\” で行継続している複数行 | 入力は REPL の irb_source()、出力は STDOUT (SJIS) | irb_source() が、たまにファイルハンドルを離してくれないのか、一時ファイルを delete できないことがある。上書きするので、おそらく動作に支障はない | |
| Groovy とだいたい同じ (本当なら、いろいろ違うんだけど…) | 入力は自前実装の REPL で “:load”、出力は STDOUT (SJIS) | :load は、通常の eval() ではなく、REPL 的に行単位で評価されるので注意。ブロック内の空行はインデントしておいてください | |
| 第 1 カラム目が空白であれば、直前の “module” までをモジュールとしてロードする。第 1 カラム目が空白でなければ、式として解釈する | モジュールの入力は REPL の “:load”、式の入力は STDIN から、出力は STDOUT | GHC では、日本語入力は Unicode に落ちるものの、出力はできないようだ | |
| カーソル行か、括弧を辿って戻れるところまで | 入力は自前 REPL の “:load” から、出力は STDOUT から | Perl の eval() は新しくブロックを作ってしまうので、評価単位内の “my”, “local” 変数や “$1″ などは、eval() が終わると破棄されてしまう。”$s =~ /([a-z]+)/; $mstr = $1″ のように複文で保持するなど要工夫 |
あとは Haskell (トップレベルの扱いがよく分からなくて作れなかった) と、JavaScript (WSH には REPL が無い) 用が欲しいところです。
Wed Apr 13 2011: JScript 用を追加。REPL 単体については「WSH JScript の REPL を書いてみた – Ayutaya.com」から。
Fri Apr 15 2011: Haskell (GHC) 用を追加。言語が言語なので、少々特殊。
このカーソル位置(↓)でマクロを起動すると、
module Test where fib 1 = 1 fib 2 = 1 fib n = fib (n - 1) + fib (n - 2) | ←
モジュールをロード。もう一度実行すれば、再ロード。
module Test where fib 1 = 1 fib 2 = 1 fib n = fib (n - 1) + fib (n - 2) [1 of 1] Compiling Test ( C:\DOCUME~1\KIICHI~1\LOCALS~1\Temp\haskell_input_2821200.hs, interpreted ) Ok, modules loaded: Test. |
続けて式を評価すると、
fib 10 ←
値が返る。
fib 10 55 ← |
Sun Apr 17 2011: Perl を追加。eval() の仕様故に、若干心残りが…。残るは PHP と VBScript くらいか。
[...] About « 秀丸エディタ用、Emacs の eval-print-last-sexp 風マクロ for Scala, Clojure, Gauche, Groo… [...]