geiser

去る11月11日は色々な記念日の集中日だった。ニュースによると、10月10日に続いて、記念日が多い特異日らしい。独身の日でアリババが売り上げ記録を更新。日本の某者も便乗したとか。 業者に取っちゃ笑いが止まらない日なんでしょうな。菓子のポッキーもこの日が記念日。

オイラーにはそんな生臭い話は無用。F の日でしょうが。1111を16進数と見做せば、Fになる。これを応用すれば、オロナミンCの日とか、ビタミンDの日ってのを作れるな。あっ、Cの表現は、1100だから、月日へのマッピングは無理っぽい。誰か屁理屈こねてみて。

有名な日として、パイの日があるな。3月14日ね。中国あたりでは別な日をパイの日にしてるみたいだけど。他に有名な日は有るか?

即興で思い付いたぞ。8月29日で肉屋の日。298を語呂合わせで読めばニクヤ。ヨーロッパ風に日付を表すと、日月年だからね。29.8.2019 ね。初めての洋行時にびっくりした記憶がある。

11月3日 サンドウィッチの日らしい。サンドウィッチの生みの親といわれている、18世紀イギリスの貴族サンドウィッチ伯ジョン・モンタギューの誕生日にちなんだそうだ。 この日の提唱は、神戸サンド屋さんの発祥。一般的には、3月13日らしい。数字の3で1を挟んでいるからとか。オイラーは、1月31日を推奨する。3が、パンに見立てた1に挟まれているから。

まあ、色々あらーな。 今日は何の日?に色々出てるぞ。

そして、来る11月22日は、いい夫婦の日だ。上皇様ご夫妻のように歳を重ねたいものだ。

gauche repl server

前回、debianにgaucheを入れて、docのhtmlを作ってしまった。ブラウザーで開いて、色々と検索してる。ファイルが一本になってるので、全体検索出来るのが嬉しい場合がある。 んで、read-eval-printなんてのを検索したら、面白い事例が、9.15 gauche.listener - リスナーに見つかった。

リモートにあるgoshに接続して、そこでreplを動かしてしまえと言う例。決して外に向いた所では実行しないで下さいと注意書きが有り。そんな訳なんでvirtual boxの中で、隠れて実行。 サーバー側の画面表示。repl.scmはブラウザーからコピペしたやつ。

(base) sakae@debian:/tmp$ gosh
gosh> ,l ./repl.scm
#t
gosh> (scheme-server 31415)
scheme server started on port 31415
client #0 from #<sockaddr inet "127.0.0.1:43852">
client #0 disconnected
client #1 from #<sockaddr inet "10.0.2.15:53374">
client #1 disconnected

クライアント側は、ncを使ってみた。これ、ネットワークのスイスアーミーナイフと自称してる便利品。

(base) sakae@debian:~$ nc localhost 31415
client[0]> (cons 888 '())
(888)
client[0]> (define (^3 n)(* n n n))
^3
client[0]> (^3 2)
8
client[0]> ^C
(base) sakae@debian:~$ nc 10.0.2.15 31415
client[1]> (cdr '(a b c))
(b c)
client[1]> (^3 3)
27
client[1]> ^C

ncコマンドの別な使い方として、接続出来るかも確認出来る。nmapみたいに隠れてじゃなくて、白昼堂々とだけど。

(base) sakae@debian:~$ nc -zv localhost 31415
localhost [127.0.0.1] 31415 (?) open
(base) sakae@debian:~$ nc -zv localhost 11111
localhost [127.0.0.1] 11111 (?) : Connection refused

ポート番号が分かっているなら、lsofコマンドが使える。これなら、ステルスだな。

(base) sakae@debian:~$ lsof -i:31415
COMMAND  PID  USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
gosh    1695 sakae    3u  IPv4  23107      0t0  TCP *:31415 (LISTEN)

root権限だと、全プロセスの該当箇所を報告。上記だと同一ユーザーだけになる。

まだ他にも方法は有るぞ。 ss(旧名netstat)を使うと、接続を待ち構えているのを確認出来る。CentOSあたりでは、netstatは古いから、新しいssを使えってさ。Pearに表示されてる奴は、どんなIPからもportからも受け付けてあげるって博愛主義の印だ。

(base) sakae@debian:~$ ss -antu      ;; or netstat
Netid  State    Recv-Q   Send-Q     Local Address:Port      Peer Address:Port
  :
tcp    LISTEN   0        5                0.0.0.0:31415          0.0.0.0:*

そして下図は、現在接続中のものの確認。

(base) sakae@debian:~$ netstat -t
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 debian:ssh              _gateway:59910          ESTABLISHED
tcp        0      0 localhost:43862         localhost:31415         ESTABLISHED
tcp        0      0 localhost:31415         localhost:43862         ESTABLISHED
(base) sakae@debian:~$

geiser start

chezを起動すると、下記のようなコマンドが発行される。

debian:~$ ps awocmd
emacs -nw hoge.ss
/usr/local/bin/scheme /home/sakae/.emacs.d/elpa/geiser-20191023.424/scheme/chez/geiser/geiser.ss

どんな風に接続されるのかな? TCPで接続されるなら、上記のような方法で、どのportを使ってるか確認出来るはずなんだけど、さっぱりお目当てのポートで待っている風ではない。 よって、geiserのソース群を家探し。

geiser-repl.el

(geiser-custom--defcustom geiser-repl-default-port 37146
   "Default port for connecting to remote REPLs."
   :type 'integer
   :group 'geiser-repl)

こんなのが引っかかってきた。でも使ってない。もしやと思って詳細に調べると

(defun geiser-connect-local (impl socket)
  "Start a new Geiser REPL connected to a remote Scheme process
over a Unix-domain socket."
  (interactive
   (list (geiser-repl--get-impl "Connect to Scheme implementation: ")
         (expand-file-name (read-file-name "Socket file name: "))))
  (let ((buffer (current-buffer)))
    (geiser-repl--start-repl impl socket)
    (geiser-repl--maybe-remember-scm-buffer buffer)))

接続方法が2種類あった。TCPを使うやつと、Unix-domainを使う方法と。デフォルトでは、Unix-domainを使うローカル方式のようだ。この方がパケットが露出しないし高速で動くからね。

Emacsでソケット通信

emacsでのサポートがどうなってるか、調べてくれた方がいた。有り難い事です。

emacsはプログラマブルなEditorと良く言われる。editorを組み立てる事を主眼に豊富な部品が用意されてるんだな。ある意味、アーミーナイフだ。要は工夫次第でどうにでもなる。ならないのは、技量が無いから。(反省しろよ >オイラー)

アーミーナイフと自称してたncはどうよ。上で使ったろうに。少しmanを漁ってみた。

UNIX DOMAIN SOCKETS
       The -U option (same as --unixsock) causes Ncat to use Unix domain
       sockets rather than network sockets. Unix domain sockets exist as an
       entry in the filesystem. You must give the name of a socket to connect
       to or to listen on. For example, to make a connection,

       ncat -U ~/unixsock

       To listen on a socket:

       ncat -l -U ~/unixsock

       Listen mode will create the socket if it doesn't exist. The socket will
       continue to exist after the program ends.

ふむ、emacsからネットワーク関連部分だけを取り出して来て、再構成するとnc(ncat)が出来上がるんだな。

このncatだけど、Creative Commons Attribution Licenseで公開されてる。最近ではNmap内に収録されてるとか。で、注意書きとして、くれぐれもspecial privileges (e.g. suid root)で使うなと言ってる。TPOをわきまえて使え、だな。

gtags global

で、これからソース観光するんだけど、slimeで見たように、定義場所や、使われている場所の列挙や、更にシンボル(変数名)まで、炙り出してくれる便利な奴を使いたい。etags *.el してTAG JUMPじゃ役不足だからね。(贅沢病です)

思い出したのは、昔使った事が有るglobalってやつ。確か多数の言語に対応してたはず。

emacsのgtagsパッケージを使ってみる

GNU GLOBALの対応言語を大幅に増やすPygmentsパーサーを導入する

globalのソースに付属してた、gtags.elをemacsのload-pathに加える。そして、キーバインドは、悩まずにslimeのそれに合わせた。

(setq load-path (append '("~/.emacs.d/lisp" ) load-path))

;; gtags global
(require 'gtags)
(global-set-key "\M-." 'gtags-find-tag)
(global-set-key "\C-c\C-w\C-c" 'gtags-find-rtag)
(global-set-key "\C-c\C-w\C-r" 'gtags-find-symbol)
(global-set-key "\M-," 'gtags-pop-stack)

検索文字列を拾う時、例えば geiser-custom--defcustom の、gに合わせてキー入力するんだけど、拾ってくれるのは、geiserまでだ。後ろの-customとかは、オイラーが補完してやらねばならぬ。とてもスイスイと飛び回る事は出来ない。コピペする鹿。

どうせコピペするなら、emacsから離れて、Webで見たいな。globalには、その為の仕掛けが用意されてる。(お世話になりました) htags(1)

$ gtags
$ htags -sanohITvt 'Welcome to Geiser source tour!'
$ firefox HTML/index.html

上の方法は、何処でも動くけど、ちと機動性に欠ける。

$ htags --suggest2
$ htags-server >& log &
$ firefox http://127.0.0.1:8000

サーバーを動かすと、機能が豊富になる。(例えば、定義場所を素早く見つけたり、grepで検索したり、これ究極の方法だな。)このサーバー機能はいつの間にかサポートされてた。

 5366 pts/0    S      0:00 /bin/sh /usr/local/bin/htags-server
 5372 pts/0    S      0:00 /home/sakae/miniconda3/bin/python
 5446 pts/3    Sl+    0:14 firefox-esr http://localhost:8000/

サーバーはpythonにお任せな風だな。 念の為、htags-serverのソースを確認。pythonでもrubyでも動くとよ。

debian:geiser$ htags-server -h
usage: htags-server [-b|--bind ip][-u use][--retry[=n]][port]
debian:geiser$ htags-server -u ruby
[2019-11-13 07:27:08] INFO  WEBrick 1.4.2
[2019-11-13 07:27:08] INFO  ruby 2.6.1 (2019-01-30) [i686-linux]
Please access at http://127.0.0.1:8000
Ruby WEBrick http/cgi server
Serving HTTP on 127.0.0.1 port 8000 ...
  :

検索文字列で補完が出来たので、ちょいと感動。裏方ではperl君が頑張っていました。

emacsにこだわるなら、 ファイルブラウザ的機能の「Speedbar」 こういうのも有るなあ。ただサポートしてる言語が限定的なのは残念であります。

geiser chicken

ここからが本題、前回の宿題を潰す。

emacsでchicken用のバッファを作り、そこでS式を評価しようとしても、無しのつぶて、難の反応もない、って問題を前回抱えてしまった。

C-x C-bしてemacsのバッファリストを眺めたら *Geiser gdb* なんてのが出来ていた。何か手がかりになりそうな事載っていないか確認。

(define (aa n) (* n n))

Error: unbound variable: geiser-eval

        Call history:

        <syntax>          (geiser-eval (quote #f) (quote (define (aa n) (* n n))))
        <syntax>          (quote #f)
        <syntax>          (##core#quote #f)

        <syntax>          (##core#quote (define (aa n) (* n n)))
        <eval>    (geiser-eval (quote #f) (quote (define (aa n) (* n n))))    <--
#;2>

なんだか評価器が無くてエラーに落ちてる。そんな馬鹿な! ちゃんとREPLは動いてプロンプト待ちになってるんだけどな。

geiserがchickenを起動する通りに、マニュアルで実行してみる。

ob$ pwd
/home/sakae/.emacs.d/elpa/geiser-20191023.424/scheme/chicken
ob$ csi -n -include-path . geiser/chicken5.scm
CHICKEN
(c) 2008-2019, The CHICKEN Team
(c) 2000-2007, Felix L. Winkelmann
Version 5.1.0 (rev 8e62f718)
openbsd-unix-clang-x86-64 [ 64bit dload ]

; loading chicken5.scm ...
; loading /usr/local/lib/chicken/11/apropos.import.so ...
; loading /usr/local/lib/chicken/11/srfi-1.import.so ...

Note: re-importing already imported identifier: assoc

Note: re-importing already imported identifier: member

Error: (import) during expansion of (import ...) - cannot import from undefined
module: srfi-18

        Call history:

        <syntax>          (##core#require library scheme#)
          :
        <syntax>          (module geiser (geiser-eval geiser-no-values geiser-newline geiser-start-server geiser-completions g...
        <syntax>          (##core#module geiser (geiser-eval geiser-no-values geiser-newline geiser-start-server geiser-comple...
        <syntax>          (import scheme apropos srfi-1 srfi-18 (chicken base) (chicken tcp) (chicken file) (chicken file posi...       <--

パッケージが足りなくて起動に失敗してるな。鶏は卵を産むって事で、chickenのパッケージシステムでは、パッケージの事をeggと称し、専用のコマンドが用意されてる。sオプションはSUDO相当で、システム領域にインストールする。このコマンドを使ってパッケージをインストール

ob$ chicken-install -s srfi-18

足りない物を追加していったら、無事に動いた。以上で宿題完了。

スキームを使い始める なんてので、軽く説明されてる。けど、メインサイトがさっぱり繋がらないんだよな。

chickenに飽きたら、C-c C-s で、他のschemeに切り替えられるし、まあいいか。

emacsでtraceするよ

これで終わってはもったいないので、課題を作り出す。

まだ、emacsのトレースをやった事が無かったので方法を探る。(いえね、maximaの時に、苦肉の策で、引っ張り出しましたから、emacsではどうかと思ってね。何せ、括弧をまとった言語ですから)

Emacs Lispのトレース(M-x trace-function)結果をorg-modeでスパッと表示

こういう重厚な物を紹介されたけど、もっとカジュアルにやってみる。題材は、geiser-gambitのソースを見てて、いかにも起動されそうな手続きを選んだ。

なお、ソースを書き換える場合もあるだろう。そんな時、ソースとバイトコンパイルの日付が一致しないと(親切にも)文句を言ってくるので、*.elcはバッサリ削除してしまった。

M-x trace-function-backend RET geiser-gambit--geiser-procedure RET してから、gambitのREPL上で実行。

*trace-output* に、永遠と下記のような記録が残っていた。

======================================================================
1 -> (geiser-gambit--geiser-procedure module-completions "\"defi\"")
1 <- geiser-gambit--geiser-procedure: "(gambit/geiser#geiser:module-completions \"defi\")"
======================================================================
1 -> (geiser-gambit--geiser-procedure eval "#f" "(gambit/geiser#geiser:module-completions \"defi\")")
1 <- geiser-gambit--geiser-procedure: "(gambit/geiser#geiser:eval #f '(gambit/geiser#geiser:module-completions \"defi\"))"
======================================================================
   :

当初は、S式が完成して、評価されるタイミングで呼び出されると思っていたけど、何だか補完情報を求めて煩雑に呼び出されている。予想を裏切る結果から、新たな発展が有るのでしょうな。

traceだけだと、呼び出しの履歴を捕まえられないので、 debug と edebugを参考にdebugを動かしてみる。

debug-on-entryで対象を登録するんだけど、簡単?モードが用意されてて、 M-x d-o-e で、いいみたいだ。

Debugger entered--entering a function:
* geiser-gambit--geiser-procedure(module-completions "\"\"")
  apply(geiser-gambit--geiser-procedure (module-completions "\"\""))
  geiser-eval--form(module-completions "\"\"")
  apply(geiser-eval--form (module-completions "\"\""))
  geiser-eval--ge(module-completions (""))
  (cond ((eq (car code) :eval) (geiser-eval--eval (cdr code))) ((eq (car code) :comp) (geiser-eval--comp (cdr code))) ((eq (c$
  (cond ((null code) "'()") ((eq code :f) "#f") ((eq code :t) "#t") ((listp code) (cond ((eq (car code) :eval) (geiser-eval--$
  geiser-eval--scheme-str((:ge module-completions ""))
  (geiser-eval--form 'eval (geiser-eval--module (nth 1 code)) (geiser-eval--scheme-str (nth 0 code)))
  geiser-eval--eval(((:ge module-completions "")))
  (cond ((eq (car code) :eval) (geiser-eval--eval (cdr code))) ((eq (car code) :comp) (geiser-eval--comp (cdr code))) ((eq (c$
  (cond ((null code) "'()") ((eq code :f) "#f") ((eq code :t) "#t") ((listp code) (cond ((eq (car code) :eval) (geiser-eval--$
  geiser-eval--scheme-str((:eval (:ge module-completions "")))
  (if (stringp code) code (geiser-eval--scheme-str code))
  geiser-eval--code-str((:eval (:ge module-completions "")))
  (geiser-con--send-string/wait (geiser-eval--connection) (geiser-eval--code-str code) 'geiser-eval--set-sync-retort timeout $
  geiser-eval--send/wait((:eval (:ge module-completions "")) nil nil)
  (geiser-eval--retort-result (geiser-eval--send/wait code timeout buffer))
  geiser-eval--send/result((:eval (:ge module-completions "")))
  geiser-completion--module-list("")
  (if modules (geiser-completion--module-list prefix) (geiser-completion--symbol-list prefix))
  geiser-completion--complete("" t)
  (and mprefix (geiser-completion--complete mprefix t))
  (let* ((prefix (and (looking-at-p "\\_>") (geiser-completion--prefix nil)))...
  geiser-company--prefix-at-point()
  (cond ((memql command ''interactive) (company-begin-backend 'geiser-company-backend)) ((memql command ''prefix) (geiser-com$
  geiser-company-backend(prefix)
  apply(geiser-company-backend prefix)
  company-call-backend-raw(prefix)
  apply(company-call-backend-raw prefix)
  company--force-sync(company-call-backend-raw (prefix) geiser-company-backend)
  company-call-backend(prefix)
  company--begin-new()
  company--perform()
  company-auto-begin()
  company-idle-begin(#<buffer * Gambit REPL *> #<window 4 on * Gambit REPL *> 409 96)
  apply(company-idle-begin (#<buffer * Gambit REPL *> #<window 4 on * Gambit REPL *> 409 96))
  timer-event-handler([t 24007 44702 388660 nil company-idle-begin (#<buffer * Gambit REPL *> #<window 4 on * Gambit REPL *> $

こちらは、起動時の状況

Debugger entered--entering a function:
* geiser-gambit--startup(nil)
  apply(geiser-gambit--startup nil)
  (progn (apply fun args))
  (if (functionp fun) (progn (apply fun args)))
  (let ((fun (or (geiser-impl--method method impl) (geiser-impl--default-method method)))) (if (funct$
  geiser-impl--call-method(repl-startup gambit nil)
  geiser-repl--startup(gambit nil)
  (let* ((prompt-rx (geiser-repl--prompt-regexp impl)) (deb-prompt-rx (geiser-repl--debugger-prompt-r$
  geiser-repl--start-repl(gambit nil)
  (let ((buffer (current-buffer))) (geiser-repl--start-repl impl nil) (geiser-repl--maybe-remember-sc$
  run-geiser(gambit)
  (cond (in-live-repl (if (and (not (eq repl buffer)) (buffer-live-p geiser-repl--last-scm-buffer)) ($
  (let* ((impl (or impl geiser-impl--implementation)) (in-repl (eq major-mode 'geiser-repl-mode)) (in$
  switch-to-geiser(nil nil #<buffer aa.scm>)
  (if arg (switch-to-geiser-module (geiser-eval--get-module) (current-buffer)) (switch-to-geiser nil $
  geiser-mode-switch-to-repl(nil)
  funcall-interactively(geiser-mode-switch-to-repl nil)
  call-interactively(geiser-mode-switch-to-repl nil nil)
  command-execute(geiser-mode-switch-to-repl)

最後のcommand-executeは、simple.elに有るやつだった。

それにしても、モジュールが分からないと、中に入り込めそうに無いなあ。

ああ、emacs向けのdebuggerの詳細が下記にあった。

17.1 Lispデバッガ

17.2 edebug

ソースレベルのdebug時には、C-u C-M-x して、対象を定義するんか。 後は、スペースキーで進めて行くなと。