slimv(4)
年初にFreeBSD 9 がリリースされていた。これってお年玉なんだろうか? 遅ればせながら 落としてきて入れてみた。だって今使ってるのは賞味期限切れの8だもの。
インストーラーがLinuxとの対抗上グラフィックバージョンになったらしいけど、VMWARE上の コンソールでは当然ながら、そんなのは拝めず。これって初日の出を見逃して悔しがる気持ちと 、ちと似てるかなあ。
インストール手順が多少変わってステップが省略されていたけど、迷わずにインストール出来た。 ここの所、色々な物を入れて楽しむってのをしなくなっていたんで、久しぶりに楽しめたよ。
仮想HDDには20Gを割付、パーテションはルートとスワップのみと言う横着仕様にした。 FreeBSDもDebianみたいにバイナリーアップデートが出来るようになってるんだけど、それの 実施時一時的に特定パーテションを使うそうな。それを見越してパーテションを切れなんて、 否現実的ですからねぇ。(退化モードに入ってるな)
portsと srcを入れた状態で、1.6Gぐらいになった。後は暇に任せて、使うパッケージを入れ、環境が 整った所で、8系からホームの引越しだな。引越しはアート引越しセンター0123へお願いすればやってくれるのかな?
取り合えずWindowsからリモートログイン出切ればいいんでGUI系なアプリは後でもいいか。 psしたら、使いもしないメールサーバーがデフォで起動してたんで止めておかないとな。 それから、不要なシリアルログイン用のプロセスも起動してるな。これ止めるには、/etc/ttysを 編集すれば良かったのかな。すっかり忘れているぞな、もし。使う時だけbootするマシンにcronは 必要だろうか? しばし悩んだのは秘密だ。
そうそう、マシンを落とす時、いちいちrootになるのかったるいので、自分のアカウントをオペレータ所属に しておこう。
csi用slime
折角の機会なのでscheme用slimeのコードを眺めてみる。まずは鶏さん用のやつね。
;; Send list `msg' as a reply back to SLIME.
(define (swank-write-packet msg out)
(let* ((string (with-output-to-string
(lambda ()
(write msg))))
(pad-hex (lambda (n)
(pad-char #\0 (pad/left 6 (num n 16)))))
(packet (fmt #f (pad-hex (string-length string)) string)))
;; This may be called by code that has set print-length-limit so we
;; have to use this kludge to avoid truncating the packet.
(##sys#with-print-length-limit #f (lambda ()
(debug-print (fmt #f "WRITE " (wrt packet)))
(display packet out)))
(flush-output out)))
これパケットの送出ルーチンだな。送りたいメッセージと(TCPの)ポートを受け取り、頭に 16進6桁のパケット長データを付け加えて送ってるんだな。
;; Tail-recursive loop to read commands from SWANK socket and dispatch them.
;; Several calls to this may be active at once e.g. when using the debugger.
(define (swank-event-loop in out)
(let* ((length (read in))
(request (read in)))
(cond
((or (eof-object? length) (eof-object? request))
(void))
(else
(debug-print (fmt #f "READ " (wrt length) (wrt request)))
(case (car request)
((:emacs-rex)
(begin
(apply swank-emacs-rex in out (swank-cleanup (cdr request)))
(swank-event-loop in out)))
((:emacs-return-string)
(cadddr request)))))))
こちらは、パケットを読み込んで処理する部分か。最初の想像では、scheme側のreplとやり取り してるかと思ったら違うのね。単にヘッダー部分を落としておいて、それをswank-cleanupで lisp語からscheme語に変換して(例 nil -> #f)、apply使って、処理してる。
で、最終的にS式を処理してるのが下記の手続きだ。
;; Evaluate an S-expression and returns a pair (condition . trace) if an
;; exception is raised or (#t . value) on success.
(define (swank-eval-or-condition sexp)
(call-with-current-continuation
(lambda (skip)
(with-exception-handler
(lambda (exn)
(let ((chain (or
((condition-property-accessor
'exn
'call-chain
#f) exn)
(get-call-chain))))
(set! *recent-call-chain* (reverse chain))
(skip (cons exn chain))))
(lambda ()
(cons #t (eval sexp)))))))
出、出たな! call-with-current-continuation。haskellの喪等とschemeのcall/ccは業界の鬼門 とされるからなあ。早速、あんちょこ見ましょ。
なんでも継続 とか Scheme:使いたい人のための継続入門 とか、、、まだまだ有って Scheme/継続の種類と利用例 とか 継続と継続渡しスタイルが参考に なるかな。嗚呼、まだ有っんで継続します。 (継続の使い方)、 継続 。
現実に戻って、他にもcall/ccが無いかと思って探してみたら、サーバーを作ってswank-event-loopを呼び出している あたりにも有ったよ。
;; The top level continuation. By invoking this we can jump out of the ;; debugger and get back to the REPL. (define *swank-top-level* #f)
(lambda ()
(call-with-values
(lambda () (tcp-accept listener))
(lambda (in out)
(call/cc (lambda (hop)
(set! *swank-top-level* hop)))
;; Whenever we escape from the debugger we'll end up here
(let ((orig-handler (current-exception-handler)))
(with-exception-handler
(lambda (exn)
(cond
(((condition-predicate 'user-interrupt) exn)
(*swank-top-level* (void)))
(else
(orig-handler exn))))
(lambda ()
(swank-event-loop in out)))))))
後は膨大にあるswank:XXX 手続き軍だな。ああ、なかなか味な誤変換するわい。 一番お世話になりそうなもの。
;; Evaluate `str' as if enclosed in (begin ...) and return the results.
(define (swank:listener-eval str)
(call-with-values
(lambda ()
(let ((forms (string->forms str)))
(if (not (null? forms))
(eval `(begin ,@forms)))))
(lambda results
`(:ok (:values ,@(map (lambda (r)
(fmt #f (wrt r)))
results))))))
Gauche用slime
次はgauche用だな。
ネットワーク関係の手続き中でdispatch-eventが呼ばれるんだな。そこで処理を振り分け ているっぽい。
(define (dispatch-event event)
(log-event "dispatch-event: ~s~%" event)
(match event
((:emacs-rex params ...) (apply emacs-rex params))
((:emacs-interrupt thread-id)
(interrupt-worker-thread thread-id))
(((or :write-string
:debug :debug-condition :debug-activate :debug-return :channel-send
:presentation-start :presentation-end
:new-package :new-features :ed :%apply :indentation-update
:eval :eval-no-wait :background-message :inspect :ping
:y-or-n-p :read-string :read-aborted :return) params ...)
(write-packet event))
(_
(log-event "Unknown event: ~s~%" event))))
んでもって、emacs-rexの中でエラー処理にガードされながら
(lambda () (write-return `(:ok ,(apply (swank-gauche: (car sexp)) (cdr sexp)))) (set! ok #t))
lambdaで囲ってるけど、これ、外側を包んでいるdynamic-windが単に手続きを3個要求してる んで、無名な手続きって事なんだな。 (参考にどうぞ) ここでもやっぱりapplyで手続きを動的に呼び出してる。 そんじゃ、一つだけ、swank系の処理を見ておくか。
;;;; Evaluation
(defslimefun listener-eval (string)
(call-with-values (lambda () (eval-repl string))
(lambda ret
(set!* /3 /2 *3 *2
/2 /1 *2 *1
/1 (if (null? ret) (values) ret)
*1 (if (null? ret) (values) (car ret)))
`(:values . ,(map write-to-string/ss ret)))))
本当に大事な所はeval-replだな。後の部分は履歴の処理か。
(define (eval-repl string)
(let ((sexp (read-from-string string)))
(if (eof-object? sexp)
(values)
(with-io-repl
(lambda ()
(set!* %3 %2
%2 %1
%1 %0
%0 sexp)
(match sexp
(('select-module module) ;; select-module is special
(eval sexp (user-env (*buffer-package*)))
((global-variable-ref 'swank-gauche 'new-package) module))
(else
(eval sexp (user-env (*buffer-package*))))))))))
vim側
余りscheme側に肩入れしてると文句が出そうなんで、今度はvim側のスクリプトも 覗いておく。まずは、swank.pyだな。
def swank_rex(action, cmd, package, thread, data=''):
"""
Send an :emacs-rex command to SWANK
"""
global id
id = id + 1
key = str(id)
actions[key] = swank_action(key, action, data)
form = '(:emacs-rex ' + cmd + ' ' + package + ' ' + thread + ' ' + str(id) + ')\n'
swank_send(form)
後は、swank_listenっていう巨大なメソッドの中で、ごにょごにょやってる。(:returnを受け取った 場合とか:okを受け取った場合とか。S式のパーサーも含まれているよ)
つらつらとこれを読んでいると、gaucheのslimeがハングした理由が分かった。モジュールなんて メッセージは想定してないのね。まあ、ローカル仕様だからしょうがないか。
んでもって、これらの蛇仕様メソッドは、slimv.vim内からいろいろな方法で呼び出されている。 例を挙げれば
python import vim
execute 'pyfile ' . g:swank_path
execute 'python swank_connect("' . g:swank_host . '", ' . g:swank_port . ', "result" )'
call SlimvCommandGetResponse( ':create-repl', 'python swank_create_repl()' )
silent execute 'python get_indent_info("' . func . '")'
vimでもPythonをこき使う
上記で、ちょいとPythonが出てきたんで、おいらもつられてpythonを使ってみる。 その為の先生は、vim上で
:h python
すれば得られる。KaoriYaさんとこのvimには、きっちりと日本語化されたdocが内蔵されてるんで 有り難い。無い人は、 vimdoc-jaを参照。
:[range]py[thon] {stmt}
Pythonのステートメント{stmt}を実行します。
:[range]py[thon] << {endmarker}
{script}
{endmarker}
Pythonのスクリプト{script}を実行します。
Note: このコマンドはPython用の機能を含めてコンパイルさ
れていないときは機能しません。エラーを抑制するには
|script-here|を参照してください。
{endmarker}の前に空白を置かないでください。"<<"の後に{endmarker}省略した時は
|:append|や|:insert|のように'.'が使われます。
この形の|:python|コマンドはVimスクリプトにPythonコードを埋め込むのに特に便利で
す。
例:
function! IcecreamInitialize()
python << EOF
class StrawberryIcecreame:
def __call__(self):
print 'EAT ME'
EOF
endfunction
Note: Pythonはインデントに関して非常に繊細です。"class"の行と"EOF"の行はまった
くインデントしないでください。
これが基本の基ね。ご丁寧にPython独自の注意書きまでしてあって、どこまで親切なんだよう。 それに引き換え、elispときたら、おめーら好きに書いていいぜって態度だもんなあ。まあ、 elisp書くよりは楽と言えば楽なんでいいけど。
でもって、つらつらと眺めて行くと、vimの画面を配列のようにアクセス出来る事が分かった。 配列だから、スライスも簡単に扱えそうで面白いな。
下記は超簡単な例
import vim
b = vim.current.buffer
print(b)
b.append("Insert from Python")
b.append( b[0] )
こんな簡単なtest.pyを用意して、
:pyfile test.py
すれば、今のバッファーの名前をステータス行に表示して、バッファーの最後に蛇からの目印を 追加し、更に1行目をコピーしてきて追加出来る。蛇を使うのやだって人なら、rubyでも同じ事が簡単に出来るよ。
所で、インポートしてるvimって何処に入ってるのかな? vimの中に組み込まれているのかな。 探してみたけど所在不明でした。
おまけ
これ、emacsが無ければやっていけない。でも時々vimの気分にも浸りたいという人向けの ものです。きっと指使いが混乱するぞ。ヒヒヒ。