r7rs-swank

前回はLemなんて言うemacs似なeditorを知ったものだから、少し戯れてしまった。そこで、directory-modeを見つけてしまったんだけど、いかにも長ったらしいモードだ。きっと何かのキーにバインドされてるだろうな、なんて考えながら、哲学の道(誰も通らない農道だけど)を散歩してたんだ。

閃いたよ。dirを指定してlemを起動しちゃえばいいんでないかい。石頭のeditorだとバイナリーファイルだけどどうするって聞いてくるだろうね。emacsだとdiredが起動するし、lemだとdirecory-modeが起動した。ユーザーの意を汲んでくれているんだね。

この際、lemでどんなキーバインドが登録されてるか確認しとく。 主な場所は、lib/coreの中。

(base) sakae@debian:core$ grep -h global-keymap *
  :
(define-key *global-keymap* "C-x C-f" 'find-file)
(define-key *global-keymap* "C-x C-r" 'read-file)
(define-key *global-keymap* "C-x C-s" 'save-buffer)
(define-key *global-keymap* "C-x ?" 'describe-key)
;; (define-key *global-keymap* "C-h m" 'describe-mode)

あれま? モードの説明はどうしちゃったんでしょうか。

探した限りでは、ホイホイとファイルを見て行くモードは無いようだ。C-x C-r で開くの面倒っぽい。通な人は、開いた限りは修正するもんだと、刷り込まれているんだろうね。

上で偉そうにemacsでdirを指定して開くとdiredモードに成るなんて書いたけど、今回が初体験です。通常は、emacsを起動して、そこからC-x d して、~/src/hoge/*.h とかと入力して、ヘッダーファイルだけを対象とかに指定してます。こうしないと目移りしちゃうからね。

後は、スペース/バックスペースで、見たいファイルに照準を合わせ、v(iew)って流れ。viewモードで閲覧中もスペース/バックスペースでページを前後に移動。身飽きたらqで元のファイル選択画面に戻る。キー操作が簡単で気に入っています。

是非この機能を実現してください。本当はツイターとかSNSで作者に希望が届けば良いのでしょうが、生憎オイラーは両方共やってません。>誰か代理で呟いてください。

ええ、勿論lemで、上下矢印、PageUp/Downで、大方の機能は実現出来る事知ってます。けど、矢印キーとか使うのは嫌いなんです。ホームポジションから指が離れてしまいますからね。

r7rs-swank

r7rs-swankには、それぞれのschemeでサーバーに成るためのshellスクリプトが用意されてる。こやつを起動して、まずはエラーにならない事が求められる。

で、OpenBSDでchickenを試してみると、r7rs不足と言われたよ。まだr7rsは特殊な世界なんだな。 chicken-install -s r7rs して、r7rsの世界に没入。 起動時の状況。

ob$ ps -ao command
COMMAND
csi -R r7rs chicken-swank.scm -b -e (start-swank) (chicken-csi)
sh chicken.sh

OpenBSDにはlsofが無いので、fstatで代用。

ob$ fstat -p 57479
USER     CMD          PID   FD MOUNT        INUM  MODE         R/W    SZ|DV
sakae    chicken-csi 57479 text /         1411734  -rwxr-xr-x     r   246336
sakae    chicken-csi 57479   wd /tmp       218688  drwxr-xr-x     r     1024
sakae    chicken-csi 57479    0 /          676081  crw--w----    rw    ttyp1
sakae    chicken-csi 57479    1 /          676081  crw--w----    rw    ttyp1
sakae    chicken-csi 57479    2 /          676081  crw--w----    rw    ttyp1
sakae    chicken-csi 57479    3* internet stream tcp 0x0 *:4005

4005で待ち受け受信の体制になってる。ならば、emacs上で M-x slime-connectすれば、繋がるはず。

Connecting to Swank on port 4005..
error in process filter: slime-dispatch-event: slime-dcase failed: (|:return| (\
|:ok| (|:pid| 123 |:style| |:spawn| |:encoding| (|:coding-systems| ("utf-8-unix\
")) |:lisp-implementation| (|:type| "Scheme" |:name| "chicken-scheme" |:version\
| 123 |:program| "/usr/bin/scheme") |:machine| (|:instance| "host" |:type| "X86\
-64") |:features| (|:swank|) |:modules| ("SWANK-ARGLISTS" "SWANK-REPL" "SWANK-P\
RESENTATIONS") |:package| (|:name| "(user)" |:prompt| "(user)") |:version| "2.2\
4")) 1)

見事にエラーなんで、記念撮影 *Messages*をインスタグラムならぬ当ページへ貼り付け。 起動側のコードにdebug?するかいフラグが有ったのでOnと言うかTeaした。

common/base.scm (define debug? #t)

ob$ sh chicken.sh
swank listening on port 4005
from slime> (:emacs-rex (swank:connection-info) "COMMON-LISP-USER" t 1)

to slime< (|:return| (|:ok| (|:pid| 123 |:style| |:spawn| |:encoding| (|:coding-systems| ("utf-8-unix")) |:lisp-implementation| (|:type| "Scheme" |:name| "chicken-scheme" |:version| 123 |:program| "/usr/bin/scheme") |:machine| (|:instance| "host" |:type| "X86-64") |:features| (|:swank|) |:modules| ("SWANK-ARGLISTS" "SWANK-REPL" "SWANK-PRESENTATIONS") |:package| (|:name| "(user)" |:prompt| "(user)") |:version| "2.24")) 1)

やり取りは正常っぽく見えるぞ。

Error: (utf8->string) bad argument type - not a structure of the required type
#!eof
u8vector

        Call history:

        <eval>    [write-packet] (string-pad-left hex-len 6 #\0)
        <eval>    [string-pad-left] (string-length string)
        <eval>    [string-pad-left] (max 0 (- size s-len))
          :
        <eval>    [read-packet] (utf8->string (read-bytevector 6 port))
        <eval>    [read-packet] (read-bytevector 6 port)        <--

でも、debugを外すと、こんなのが出てくるからなあ。https://www.call-cc.org/には、相変わらず繋がらない。call/ccが継続してないじゃん。

kawa

取り合えずにわとりは置いておいて、新種のschemeであるkawaを試してみるか。

The Kawa Scheme language

javaの上に構築されてるschemeなんで、

java -jar kawa.jar

で起動出来るはずなんだけど、何故か/usr/local/bin/kawaはバイナリーファイルになってる。そして普通に起動すると

java -Dkawa.command.line=kawa kawa.repl --connect 6160

4005で待っていないのが、詐欺っぽいな。なんせ日本では、kawa-sagi なんて鳥もいますから。swankするかお試しする。

ob$ sh kawa.sh
/tmp/r7rs-swank/common/handlers.scm:358:29: '::' must follow parameter name
/tmp/r7rs-swank/common/handlers.scm:358:29: misformed formals in lambda
/tmp/r7rs-swank/common/handlers.scm:361:29: '::' must follow parameter name
/tmp/r7rs-swank/common/handlers.scm:361:29: misformed formals in lambda
/tmp/r7rs-swank/specific/kawa.scm:22:29: warning - no known slot 'accept' in jav
a.lang.Object
/tmp/r7rs-swank/specific/kawa.scm:23:17: warning - no known slot 'getInputStream' in java.lang.Object
/tmp/r7rs-swank/specific/kawa.scm:23:73: warning - no known slot 'getOutputStream' in java.lang.Object
  :

見事に離陸失敗、残念至極。

slime/contrib/swank-kawa.scmなコードが綺麗で、参考になるな。実際に試すにはJavaとかを良く知らないと駄目ぽいけど。

at racket on CentOS

舞台をCentOSに移して、各種のschemeでやってみよう。何せコレクションしていますから。一番期待の持てそうなracketです。こやつは、黒い窓恐いの若者向けにDrRacketなんてのを手掛けてますからね。

(base) [sakae@c8 r7rs-swank-Gauche-custom]$ sh racket.sh
standard-module-name-resolver: collection not found
  for module path: r7rs/lang/reader
  collection: "r7rs/lang"
  in collection directories:
   /home/sakae/.racket/7.4/collects
   /usr/local/racket/collects
   ... [160 additional linked and package directories]
  context...:
   show-collection-err
    :

で、早速エラーになりました。どうやらr7rsは先端過ぎて、通常のインストールでは関係者が同居してないようです。

(base) [sakae@c8 r7rs-swank-Gauche-custom]$ raco pkg install r7rs
Resolving "r7rs" via https://download.racket-lang.org/releases/7.4/catalog/
Resolving "r7rs" via https://pkgs.racket-lang.org
Downloading repository git://github.com/lexi-lambda/racket-r7rs?path=r7rs
  :
raco setup: 1 making: <pkgs>/r7rs-lib
raco setup: 1 making: <pkgs>/r7rs-lib/lang
raco setup: 1 making: <pkgs>/r7rs-lib/lang/private
raco setup: 1 making: <pkgs>/r7rs-lib/load
raco setup: 1 making: <pkgs>/r7rs-lib/load/lang
raco setup: 1 making: <pkgs>/r7rs-lib/private
raco setup: --- creating launchers ---                             [14:12:52]
raco setup: --- installing man pages ---                           [14:12:52]
raco setup: --- building documentation ---                         [14:12:52]
raco setup: --- installing collections ---                         [14:12:52]
raco setup: --- post-installing collections ---                    [14:12:52]

racoとか言うパッケージマネージャーを使って、入れてあげます。これで、サーバー側は起動しました。

(base) [sakae@c8 ~]$ ps a
  2927 pts/2    Sl+    0:01 racket -I r7rs racket-swank.sld
(base) [sakae@c8 ~]$ lsof -p 2927
racket-sw 2927 sakae    5u     IPv6  52869      0t0      TCP *:pxc-pin (LISTEN)
racket-sw 2927 sakae    7u     IPv4  52870      0t0      TCP *:pxc-pin (LISTEN)
(base) [sakae@c8 ~]$ grep pxc-pin /etc/services
pxc-pin         4005/tcp                # pxc-pin
pxc-pin         4005/udp                # pxc-pin

lsofでポートを確認したら、変なやつが出てきたので、素の番号を調べてみました。slimeのデフォのポート番号だよって、出願しなかったものだから、変な名前で登録されてるではないですか。弱小企業slime。

サーバーを立てておいてから、emacsで M-x slime-connectします。

Scheme  Port: 4005  Pid: 123
; SLIME 2.24
(user)> (version)
"7.4"
(user)> (define (hoge n) (* n n n))
#<void>
(user)> (hoge 5)
125

嘘のPidと言うか、問い合わせた時の返答をそのまま表示してます。一応replとしては動いているようです。(miniBufferにグダグダとエラーのメッセージが出てきますが)

at gauche

hamayamaさんのパッチが当たったものを使うと

Scheme  Port: 4005  Pid: 3434
; SLIME 2.24
(user)> (gauche-version)
"0.9.8"

正しいPidを表示してます。補完も効くし、後はedit-bufferとの連携ですかね。宜しくお願いします。

swank.rb

slime/contribの中に面白い物を発見。

debian:tmp$ ruby -r ./swank -e swank
Listening on ["AF_INET6", 4005, "::1", "::1"]
dispatch: [:":emacs-rex", [:"swank:connection-info"], "COMMON-LISP-USER", :t, 1]
dispatch: [:":emacs-rex", [:"swank:swank-require", [:quote, [:"swank-trace-dialog", :"swank-package-fu", :"swank-presentations", :"swank-macrostep", :"swank-fuzzy", :"swank-fancy-inspector", :"swank-c-p-c", :"swank-arglists", :"swank-repl"]]], "COMMON-LISP-USER", :t, 2]

M-x slime-connectすると、

Undefined function: swank:swank-require
  [RuntimeError]

Restarts:
 0: [Quit] SLIME top-level.

Backtrace:
  0: /tmp/swank.rb:80:in `emacs_rex'
  1: /tmp/swank.rb:64:in `dispatch'
  2: /tmp/swank.rb:49:in `block in main_loop'
  3: /tmp/swank.rb:48:in `catch'
  4: /tmp/swank.rb:48:in `main_loop'
  5: /tmp/swank.rb:42:in `serve'
  6: /tmp/swank.rb:29:in `accept_connections'
  7: /tmp/swank.rb:14:in `swank'
  8: -e:1:in `<main>'

哀れな事に、未実装とな。でも、まあ通信は出来ているって事で誉めてあげましょう。

この他、swank-mlworks.smlなんて言うSML系のやつもいた。骨休めに眺めてみると面白いかも。

r7rs-swank-gauche

r7rs-gaucheなサーバーを起動しておいて、emacsからconnectしpromptが出てくるまでのログ。

swank listening on port 4005
from slime> (:emacs-rex (swank:connection-info) "COMMON-LISP-USER" t 1)

to slime< (:return (:ok (:pid 2812 :style :spawn :encoding (:coding-systems ("utf-8-unix")) :lisp-implementation (:type "Scheme" :name "gauche-scheme" :version
"0.9.8" :program "gosh") :machine (:instance "host" :type "X86-64") :features (:swank) :modules ("SWANK-ARGLISTS" "SWANK-REPL" "SWANK-PRESENTATIONS") :package (:name "(user)" :prompt "(user)") :version "2.24")) 1)
from slime> (:emacs-rex (swank:swank-require (quote (swank-trace-dialog swank-package-fu swank-presentations swank-macrostep swank-fuzzy swank-fancy-inspector swank-c-p-c swank-arglists swank-repl))) "COMMON-LISP-USER" t 2)

to slime< (:return (:ok ()) 2)
from slime> (:emacs-rex (swank:init-presentations) "COMMON-LISP-USER" t 3)

to slime< (:return (:ok ()) 3)
from slime> (:emacs-rex (swank-repl:create-repl nil :coding-system "utf-8-unix") "COMMON-LISP-USER" t 4)

to slime< (:return (:ok ("(user)" "(user)")) 4)

replから、(cdr '(a b c)) を評価してみた。

from slime> (:emacs-rex (swank:autodoc (quote ("cd" swank::%cursor-marker%)) :pr
int-right-margin 80) "(user)" :repl-thread 5)

to slime< (:return (:abort "unbound variable: cd: ") 5)
from slime> (:emacs-rex (swank:autodoc (quote ("cdr" swank::%cursor-marker%)) :p
rint-right-margin 80) "(user)" :repl-thread 6)

to slime< (:return (:ok ("(===> cdr <=== obj)" t)) 6)
from slime> (:emacs-rex (swank:autodoc (quote ("cdr" "" swank::%cursor-marker%))
 :print-right-margin 80) "(user)" :repl-thread 7)

to slime< (:return (:ok ("(cdr ===> obj <===)" t)) 7)
from slime> (:emacs-rex (swank:autodoc (quote ("cdr" "'" swank::%cursor-marker%$) :print-right-margin 80) "(user)" :repl-thread 8)

to slime< (:return (:ok ("(cdr ===> obj <===)" t)) 8)
from slime> (:emacs-rex (swank:autodoc (quote ("cdr" ("" swank::%cursor-marker%$)) :print-right-margin 80) "(user)" :repl-thread 9)

to slime< (:return (:abort "unbound variable: ||: ") 9)
from slime> (:emacs-rex (swank:autodoc (quote ("cdr" ("a" "" swank::%cursor-mar$er%))) :print-right-margin 80) "(user)" :repl-thread 10)

to slime< (:return (:abort "unbound variable: a: ") 10)
from slime> (:emacs-rex (swank:autodoc (quote ("cdr" ("a" "b" "" swank::%cursor$marker%))) :print-right-margin 80) "(user)" :repl-thread 11)

to slime< (:return (:abort "unbound variable: a: ") 11)
from slime> (:emacs-rex (swank:autodoc (quote ("cdr" ("a" "b" "c" swank::%curso$-marker%))) :print-right-margin 80) "(user)" :repl-thread 12)

to slime< (:return (:abort "unbound variable: a: ") 12)
from slime> (:emacs-rex (swank-repl:listener-eval "(cdr '(a b c))
") "(user)" :repl-thread 13)

to slime< (:write-string "")
to slime< (:presentation-start 1 :repl-result)
to slime< (:write-string "(b c)" :repl-result)
to slime< (:presentation-end 1 :repl-result)
to slime< (:write-string "\n" :repl-result)
to slime< (:return (:ok ()) 13)

(cdr まで入力した所で、関数名が確定し、miniBufferには、(cdr obj) って、案内が出てきた。

from slime> (:emacs-rex (swank:completions "cad" (quote "(user)")) "(user)" :repl-thread 36)

to slime< (:return (:ok (("caddar" "cadar" "cadddr" "cadadr" "caddr" "cadr" "cadaar" "cadaar" "cadddr" "caddar" "cadadr" "caddr" "cadar") "cad")) 36)
from slime> (:emacs-rex (swank:autodoc (quote ("cad" swank::%cursor-marker%)) :print-right-margin 80) "(user)" :repl-thread 37)

to slime< (:return (:abort "unbound variable: cad: ") 37)

cadまで入力してTABを叩くと補完の問い合わせがgaucheに行く。それに答えて候補リストが返るとな。問い37は、候補をその場で選ぶような仕様だろ(lemでの挙動)。実験してるのは、普通のemacsなんで、エラーが返っているんだな。

from slime> (:emacs-rex (swank:autodoc (quote ("cadr" swank::%cursor-marker%)) :print-right-margin 80) "(user)" :repl-thread 38)

to slime< (:return (:ok ("(===> cadr <=== obj)" t)) 38)
from slime> (:emacs-rex (swank:autodoc (quote ("cadr" "" swank::%cursor-marker%)) :print-right-margin 80) "(user)" :repl-thread 39)

to slime< (:return (:ok ("(cadr ===> obj <===)" t)) 39)
from slime> (:emacs-rex (swank:autodoc (quote ("cadr" "'" swank::%cursor-marker%)) :print-right-margin 80) "(user)" :repl-thread 40)

to slime< (:return (:ok ("(cadr ===> obj <===)" t)) 40)

  :
from slime> (:emacs-rex (swank:autodoc (quote ("cadr" ("a" "b" "c" "" swank::%cursor-marker%))) :print-right-margin 80) "(user)" :repl-thread 44)

to slime< (:return (:abort "unbound variable: a: ") 44)

cadrって確定させたので、autodocの説明要求ね。 手に取るように、やり取りが確認出来て面白い。

handler

r7rs-swankは、emacsからアクセスされるサーバーだ。クライアントからは、色々な要求が飛んでくる。その要求の一つ一つに対して、どんな返答をしたらいいかが、handlers.scmに定義されてる。

冒頭にはマクロが定義されてて、ハンドラーの定義が間違いなく書けるように配慮されてた。

(base) [sakae@c8 common]$ grep define-slime-handler handlers.scm
(define-syntax define-slime-handler
    ((define-slime-handler (name . params) body0 body1 ...)
(define-slime-handler (:emacs-rex sexp env-name thread id)
(define-slime-handler (swank:connection-info)
(define-slime-handler (swank:swank-require packages)
  :
(define-slime-handler (swank::menu-choices-for-presentation-id id)
(define-slime-handler (swank::describe-to-string object)
  (define-slime-handler (swank:find-definitions-for-emacs name)

幾つあるかと数えたら、50個を超えてた。slimeって奴は、本当に色々と要求を出すものだ。

代表的なやつを見てみる。要求を受領して実行してね。

(define-slime-handler (:emacs-rex sexp env-name thread id)
  (call-with-current-continuation
   (lambda (exit)
     (parameterize ((param:abort (lambda (message)
                                   (exit `(:return (:abort ,message)
                                                   ,id))))
                    (param:environment ($environment env-name))
                    (param:current-id id))
       `(:return (:ok ,(process-form sexp env-name))
                 ,id)))))

おっ、ここでも有名なcall/ccが出てきた。parameterizeを使って、評価を失敗したら、エラーで脱出(継続)するように設定。そうしておいてから要求の有ったsexpを評価。

実際の評価は、base.scm/process-formに有った。

(define (process-form form env-name)
  (let ((key (car form)))
    (let ((h (find-handler key)))
      (if h
          (with-exception-handler
           (lambda (condition)
             ($handle-condition condition)
             (swank/abort ($error-description condition)))
           (lambda () (apply h (cdr form))))
          `(:return (:abort "no handler found") nil)))))

gauche 0.9.9

リリース 0.9.9

おめでとうございます。オイラーも早速更新。

gauche-config --reconfigure | sh
make
 :
gosh -V
Gauche scheme shell, version 0.9.9 [utf-8,pthreads], i686-pc-linux-gnu
(version "0.9.9")
(command "gosh")
(scheme-id gauche)
(language scheme r5rs r7rs)
(website "https://practical-scheme.net/gauche")
(platform "i686-pc-linux-gnu")

これがr7rs流の自己紹介?

debian:r7rs-swank-Gauche-custom$ sh gauche.sh
gosh: "ERROR": uninitialized variable: symbol->string
  :
gosh> (symbol->string 'cons)
"cons"

あれ?

debian:r7rs-swank-Gauche-custom$ git pull
remote: Enumerating objects: 13, done.
remote: Counting objects: 100% (13/13), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 7 (delta 5), reused 7 (delta 5), pack-reused 0
Unpacking objects: 100% (7/7), done.
From https://github.com/Hamayama/r7rs-swank-Gauche-custom
   8d7e3d1..3fe0042  master     -> origin/master
Updating 8d7e3d1..3fe0042
Fast-forward
 README.md           |  3 ++-
 gauche-main.scm     |  7 ++++++-
 gauche-swank.sld    | 16 ++++------------
 specific/gauche.scm |  7 ++++---
 4 files changed, 16 insertions(+), 17 deletions(-)
debian:r7rs-swank-Gauche-custom$ sh gauche.sh

もう追従されてる。仕事早いな。