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はプログラマブルな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ってやつ。確か多数の言語に対応してたはず。
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の詳細が下記にあった。
ソースレベルのdebug時には、C-u C-M-x して、対象を定義するんか。 後は、スペースキーで進めて行くなと。