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の中に組み込まれているのかな。 探してみたけど所在不明でした。

おまけ

Vimpulse

これ、emacsが無ければやっていけない。でも時々vimの気分にも浸りたいという人向けの ものです。きっと指使いが混乱するぞ。ヒヒヒ。