秀丸で Scheme を効率よく開発したい
Scheme で快適に開発するには Emacs が最適だというのは承知しているけど、どうしてもなじめない。こればっかりは仕方がない。
なので、快適に開発を行うためにはまず快適環境から作らなければならない。
僕の場合、手になじむエディタは秀丸なので、秀丸で Scheme を快適に編集、という方向で考える。
インタプリタとの連携
秀丸 Ver 8 から、標準出力のアウトプット枠、という機能が追加され、秀丸マクロに新設された runex で実行した外部プログラムの標準出力をそこに出力することができるようになった。
これを使用して、現在編集中の S 式を簡単に評価しよう、と思って
gosh.exe -E"print (foo bar baz)" -E"exit"
みたいなコマンドラインを実行するマクロを作ってはみたけど、これだと単一の S 式しか評価できない。例えば上記例だと foo も bar も baz も define されているんだけど、それらが解釈できない。もしやるとすると
gosh.exe -e"(define foo +)" -e"(define bar 1)" -e"(define baz 2)" -E"print (foo bar baz)" -E"exit"
というコマンドラインを作らなければならない。
これをコマンドラインで作り出すのは無理なので、アウトプット枠を使用したマクロという方式はやめて、秀丸からインタプリタと会話ができるインターフェイスが必要、という結論になった。
僕が使用している Scheme 処理系は Gauche だが、Gauche に限らず、他の処理系も起動するとコマンドプロンプト上で動作する。コマンドプロンプトを Spy++ で観察して、ペーストされたときにどんなメッセージを送ればよいのか調べてみようとしたところ、コマンドプロンプトは Spy++ でメッセージを覗けないようになっている。おそらくセキュリティの問題だと思う。
なのでコンソールアプリのフロントエンド、しかも自分で改造可能なものが必要になって、最近 ckw をいじっている。
ckw に WM_COPYDATA のインターフェイスを用意し、秀丸マクロから DLL 経由で WM_COPYDATA を送ってやれば、秀丸とインタプリタのシームレスな連携が可能になる。
ソースコードの見た目
Scheme のコメントは
; これが基本的な一行コメント #;(+ 1 (+ 2 3) (+ 4 5)) ; S 式コメント #| ブロックコメント。 #| ネスト可能 |# |#
こんな感じで(はてなでは S 式コメントとブロックコメントが認識されないけど)、これを秀丸上でコメントとして認識させる。
"..." と #\. も文字列として認識させる。
これは見た目がわかりやすくなる以外に、後述するマクロで重要な位置を占める。
カーソル移動マクロ
Emacs では、特定のキーバインドで S 式単位の移動ができる。
これは便利そうなので、秀丸でも実現させる。
disabledraw; #x = x; #y = y; #n = 0; while (true) { $t = gettext2(column, lineno, column + 2, lineno); if (code == eof) { moveto #x, #y; endmacro; } else if (colorcode != 3 && colorcode != 20) { if ($t == "'(" || code == '(') { if (#n == 0 && (#x != x || #y != y)) { endmacro; } else { #n = #n + 1; } if (code == '\'') { right; } } else if (code == ')') { #n = #n - 1; if (#n < 0) { #n = 0; } if (#n == 0) { break; } } } right; } #x2 = x; #y2 = y; while (true) { $t = gettext2(column, lineno, column + 2, lineno); if (colorcode != 3 && colorcode != 20) { if ($t == "'(" || code == '(') { break; } else if (code == ')') { if ((#xp == 0 && #yp == 0) && (#x != x || #y != y)) { #xp = x; #yp = y; } } } if (code == eof) { if (#x == #x2 && #y == #y2 && (#xp != 0 || #yp != 0)) { #x2 = #xp; #y2 = #yp; } moveto #x2, #y2; break; } right; }
移動系のマクロはいっぱいあるので全部は貼らないけど、これは次の S 式に移動するマクロ。
if (colorcode != 3 && colorcode != 20) {
上記が「現在のカーソル位置がコメント(3)でなく文字列(20)でもないなら」という条件式で、要するにコメント内の S 式や文字列内の S 式に間違って移動しないようにしているわけだ。
マクロを正確に動作させるためには、ファイルタイプ別の強調表示の設定は非常に重要。
ソースの自動整形
Emacs などでは、改行入力時に自動的に適切に整形(pretty print というらしい)されるようだ。
これは便利そうなので秀丸でも実装してみた。
begingroupundo; disabledraw; if (selecting) { delete; } if (filetype == ".scm") { #column = column; #lineno = lineno; if (#column == 0) { goto out; } while (true) { left; if (code == '(' && colorcode == 0) { #p = #p + 1; execmacro "scheme_is_toplevel.mac"; $r = getresultex(-1); if ($r == "invalid" || $r == "top") { break; } } else if (code == ')' && colorcode == 0) { #p = #p - 1; } if (column == 0 && lineno == 1) { break; } } movetolineno #column + 1, #lineno; } out: insertreturn; while (#p > 0) { insert " "; #p = #p - 1; } enabledraw; endgroupundo;
このマクロを Enter を押したときに実行されるように仕込む。
欠点としては、内部で別のマクロを呼び出していて、そのマクロがソースコード全体をなめるので、コードが大きければ大きいほど時間がかかるという点。大体 400 ステップの Scheme コードで、Enter 一回あたり 5 秒程度かかってしまい、使い物にならない。
Artistic Style の Scheme 版みたいなツールはないものか。探してみたけど見つからない。なければ作るか。
作るならせっかくなので Scheme で作りたい。
こんな仕様:
- 現在編集中のソースコードとカーソル位置を渡す
- その場所で改行された場合、どれだけインデントすればよいか返す
秀丸上で Enter を入力するたびに Scheme インタプリタが実行される、というのがいささか気になるので、もしかしたら最初から C で作ったほうが良いのかも、と思ったり思わなかったり。