elisp

何気に、 Vimconf 2018のスタッフをしてきたなんてのを見ていたら、面白い機能が紹介されてた。

何とvimからgdbを操れるようになったとの事。最近の事らしい。やっぱりemacsに追いつけ追い越せが成せる情熱なんでしょうかね。

オイラーも追試したくなったぞ。vimが入っていない、Debian32bit版で試してみるかな。 The official Vim repositoryに行って、cloneしてくる。歴史あるやつなんで、落とすまでたっぷり時間がかかるかと思っていたら、案外あっさりと落ちてきた。

srcへ移動して、configure; make install するだけ。何の悩みも無くインストール完了。

debian:~$ ls -l MINE/bin
total 2836
lrwxrwxrwx 1 sakae sakae       3 Nov 29 14:35 ex -> vim*
lrwxrwxrwx 1 sakae sakae       3 Nov 29 14:35 rview -> vim*
lrwxrwxrwx 1 sakae sakae       3 Nov 29 14:35 rvim -> vim*
lrwxrwxrwx 1 sakae sakae       3 Nov 29 14:35 view -> vim*
-rwxr-xr-x 1 sakae sakae 2883396 Nov 29 14:35 vim*
lrwxrwxrwx 1 sakae sakae       3 Nov 29 14:35 vimdiff -> vim*
-rwxr-xr-x 1 sakae sakae    2121 Nov 29 14:35 vimtutor*
-rwxr-xr-x 1 sakae sakae   14012 Nov 29 14:35 xxd*

随分と太っているけど(それでもstripされてる)、golangが作るバイナリーとどっこい々なんで、今更驚くような事は無い。

で、お目当てのgdbだけど、最初はプラグインだかをロードするんかな。 :packadd termdebug って、まずは宣言。次は、C語のdebug対象をコンパイル。そして、:Termdebug a.out する。(大文字に注意)

Reading symbols from a.out...done.
(gdb) new-ui mi /dev/pts/6
New UI allocated
(gdb) b std.c:6
Breakpoint 1 at 0x5f0: file std.c, line 6.
(gdb) r
Starting program: /home/sakae/work/a.out

Breakpoint 1, main () at std.c:6
6               return puts("");
(gdb)
!gdb [running] =============================================  1,1            All
Hello


debugged program [active] ==================================  1,3            All
  Step    Next    Finish    Cont    Stop    Eval
  #include <unistd.h>
  #include <stdio.h>

  int main(){
          write(0, "Hello", 5);
1         return puts("");
  }
  ~
  ~
~/work/std.c =================================================================

こんな具合に画面が3個に割れる。(分かり易いようにstatus-lineには、=== を入れておいた)

上段はgdbのコンソール。中段は、対象プログラムの端末として、表示と入力に供される。下段は、ソースの表示。止まった行が反転してる。(最左カラムが1ってなってるのは、BP番号だろう) 下段の画面にStepとかNextとか出てるのは、マウスのクリック用ボタンなんだろうね。viなのに、マウスでグリグり出来るって、何ってこっちゃ! 呆れて、もとえ素晴らし過ぎて、声も出ませんよ。

勝手に3分割されちゃったけど、画面が狭い端末じゃ、窮屈だわい。vimは封印だな。

まてまて、折角だから労作のソースでも見ておくか。 /home/sakae/src/vim/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim ってのがそれ。emacsから開いたら、色も付かないし毛嫌いされたもんだ。ちゃんとvimから開いてあげよう。

ただソースを眺めるならrvimで開けばいいんだな。less風にSPCとかBackキーで、うろうろとソースを眺めるには、どうするんかな?

lessコマンドの代わりにvimエディタを使用が有った。起動用スクリプトが用意されてて、less.vimが呼ばれるようだ。って事はvim上からless.vimを呼べるはずだよな。深入りしそうなので、ここでSTOPしておく。

Vim日本語ドキュメント

:runtime macros/less.vim

これで色付き(まるで素人ぽい言い方だな)lessが誕生する。コメントがオイラーの所では青色になるけど、微妙な色だな。どこをつつけば色が変わるの? 調べ出すと色きちがいになりそうなので、これぐらいにしておくか。

elisp

前回、ひょんなことからgo-autocomleteに出会った。作者さんの解説にも行きついた。 vim用の言語は遠慮するけど、emacsの根幹を成しているlispなら、オイラーの脳とも親和性が高い(と思われる)。それにlispって事で、潰しが効くしね。その点、vimのスクリプトは、、、、、、、。

この際だからelispにハマってみるかな。SDでもvim派emacs派でつり合いが取れるように連載が 行われているので、記事の発掘は容易だろう。

emacs(elisp)推進委員会の有名所が、惜しげもなく記事を公開されてた。これはもう、じっくりと読み込む鹿。

「Emacsマスターへの道」

「Emacsのトラノマキ」

「るびきち流Emacs超入門」の記事一覧

上記の中にあった記事で、 Emacs Lisp(実践編) が、非常に参考になったぞ。

info

emacsのhelp関係は、infoとしてまとめられている。勿論英語だ。不得意なオイラーは、

emacs XX.xの日本語マニュアル を参照。更に、根底を成すlispまで足を延ばそうとしたら、 Programming in Emacs Lispを見る事になる。ちょっと版が古いけど、大筋に違いは無いだろう。

GNU Emacs Lispリファレンスマニュアル(old)

Emacs Lispこれをググル様の翻訳機にかけて、読むのも一興かと。

Inside emacs

「Emacs Lispの実装」に倣って、動いているemacsにattachしようとしたら、attachした瞬間に相手側がkillされちゃったぞ。これって、CentOSの保護機能でも働いているのかな?

しょうがないので、

gdbserver :1234 emacs

とやっておいて、別のemacsで、 M-x gdb emacs して、target remote 1234 して、入る事にした。

(gdb) bt
  :
#5  0x000000000049bd6c in read_char (commandflag=commandflag@entry=1, map=map@entry=24064323, prev_event=<optimized out>, used_mouse_menu=used_mouse_menu@entry=0x7fffffffdfeb, end_time=end_time@entry=0x0) at keyboard.c:2717
#6  0x000000000049cad3 in read_key_sequence (keybuf=keybuf@entry=0x7fffffffe0c0, prompt=prompt@entry=0, dont_downcase_last=dont_downcase_last@entry=false, can_return_switch_frame=can_return_switch_frame@entry=true, fix_current_buffer=fix_current_buffer@entry=true, prevent_redisplay=prevent_redisplay@entry=false, bufsize=30) at keyboard.c:9147
#7  0x000000000049e516 in command_loop_1 () at keyboard.c:1368
#8  0x00000000005021e4 in internal_condition_case (bfun=bfun@entry=0x49e310 <command_loop_1>, handlers=handlers@entry=17232, hfun=hfun@entry=0x4955c0 <cmd_error>) at eval.c:1332
#9  0x0000000000490d8c in command_loop_2 (ignore=ignore@entry=0) at keyboard.c:1110
#10 0x0000000000502194 in internal_catch (tag=tag@entry=44784, func=func@entry=0x490d70 <command_loop_2>, arg=arg@entry=0) at eval.c:1097
#11 0x0000000000490d47 in command_loop () at keyboard.c:1089
#12 0x00000000004951ee in recursive_edit_1 () at keyboard.c:695
#13 0x0000000000495505 in Frecursive_edit () at keyboard.c:766
#14 0x0000000000409183 in main (argc=<optimized out>, argv=0x7fffffffe448) at e\
macs.c:1713

apply_lambdaあたりにBPを置いておくと良いかも。ああ、ここじゃ五月蝿過ぎるな。

同じ事をdebianでやってみたら、普通にattach出来て、何食わぬ顔で観光出来たよ。やっぱりCentOSは、ビジネス用にチューニングされてるのね。

mode

Emacsモード作成手順

Emacs Mode Tutorial 訳 ( Emacs メジャーモード作成チュートリアル)

Emacs でオレオレ言語用モードを簡単作成! (ヘルプもあるよ!)

これ面白いな。ああ、面白いと言えば、Matzさんのオレオレ言語、ruby。メソッドのbodyが並みな波型カッコじゃなくて、do end とかと、こだわりを見せている。

これって、emacsで上手く処理出来るモード、ruby-modeを上手く定義出来たからとか。言語を助ける環境も含めて、人気度が決まるんだなあ。

edebug

Emacs Lispのソースコードデバッガ edebug を使うとかを見ると、楽しい事が待っている。

早速実習。例は、上で出て来たお勧めのやつ。最後の所にリストが載ってるけど、掲載の都合上、平べったいままだ。よって整形する。

各関数の冒頭 (defun ... にカーソルを持って行って、C-M-q すれば、整形される。 (undo for C-_)

edebugで追いたいのは、list-methodsなんだけど、先にこのファイルをロードする。M-x load-file mode.el しとく。関数の中で、C-u C-M-x する。 そして、サンプルのTest.javaが見えてる所で、M-x list-methods する。

(defun list-methods ()
  (interactive)
=>(let ((methods (list-methods-get-methods (current-buffer)))
        (buffer (get-buffer-create "*list-methods*")))
          :

edebuggerが起動した。どんなedebugコマンドが有るかは ? する。

SPC             edebug-step-mode
-               negative-argument
=               edebug-temp-display-freq-count
?               edebug-help
B               edebug-next-breakpoint
C               edebug-Continue-fast-mode
E               edebug-visit-eval-list
G               edebug-Go-nonstop-mode
I               edebug-instrument-callee
:

まあ、山のようにコマンドが揃っているわい。そろりそろりと使ってみるのが良かろう。

普通に使うなら、SPCを叩くだけ。stepperとして機能する。そんなまどろっこしい事はしないという向きには、カーソルを止めたい所まで移動し、そこで b する。breakを張るんだな。 継続は g だ。

C-h ?

emacsのHelpへの入り口は、C-h ? だ。オイラーの知らない(使った事が無い)やつが沢山列挙されてたぞ。例えば、C-h C-e

8.3 Where can I get Emacs Lisp packages that don’t come with Emacs?
===================================================================

The easiest way to add more features to your Emacs is to use the command
‘M-x list-packages’.  This contacts the GNU ELPA (https:///elpa.gnu.org)
(“Emacs Lisp Package Archive”) server and fetches the list of additional
packages that it offers.  These are GNU packages that are available for
use with Emacs, but are distributed separately from Emacs itself, for
reasons of space, etc.  You can browse the resulting ‘*Packages*’ buffer
to see what is available, and then Emacs can automatically download and
install the packages that you select.  See (emacs)Packages.

   There are other, non-GNU, Emacs Lisp package servers, including:
MELPA (http://melpa.org/); and Marmalade (https://marmalade-repo.org/).
To use additional package servers, customize the ‘package-archives’
variable.  Be aware that installing a package can run arbitrary code, so
only add sources that you trust.
  :

とか言うのが出て来た。お勧めを探す楽しみが有るな。

C-h o XXX で、emacsが知ってるXXX(funcでもvalでも)を教えてくれる。楽ちん。

で、XXXの所にload-pathを入れてみる。

load-path is a variable defined in ‘C source code’.
Its value is
("/home/sakae/.emacs.d/elpa/go-autocomplete-20170626.1023" "....")

  This variable may be risky if used as a file-local variable.

Documentation:
List of directories to search for files to load.
Each element is a string (directory file name) or nil (meaning
‘default-directory’).
This list is consulted by the ‘require’ function.
  :

とか出て来た。emacs用のPATHである。現在値が列挙されたけど、何処かのOSのみたいに、非常に醜い(人間に取ってね)。S式なんで、elispでは、おちゃのこさいさいではあるが。 どんな優先順位になってるか、興味が有るので、見やすく表示させてみる。

こういうのは、*scratch* bufferで試すのが常道。リストのエレメントを一つづつ取り出して何かやるってのは良くある事なので、subr.elの冒頭の方にしっかり定義されている。

(defmacro dolist (spec &rest body)
  (declare (indent 1) (debug ((symbolp form &optional form) body)))
  (unless (consp spec)
    (signal 'wrong-type-argument (list 'consp spec)))
  (unless (<= 2 (length spec) 3)
    (signal 'wrong-number-of-arguments (list '(2 . 3) (length spec))))
  (let ((temp '--dolist-tail--))
    (if lexical-binding
        `(let ((,temp ,(nth 1 spec)))
           (while ,temp
             (let ((,(car spec) (car ,temp)))
               ,@body
               (setq ,temp (cdr ,temp))))
           ,@(cdr (cdr spec)))
      `(let ((,temp ,(nth 1 spec))
             ,(car spec))
         (while ,temp
           (setq ,(car spec) (car ,temp))
           ,@body
           (setq ,temp (cdr ,temp)))
         ,@(if (cdr (cdr spec))
               `((setq ,(car spec) nil) ,@(cdr (cdr spec))))))))

マクロですかい。大変だな、と言っておこう。大変さを裏に隠してあるんで、使い方は簡単。 エレメントを一つづつxって変数に取り出して、印字してから改行って流れ。printを使っちゃうと、余計な改行が入ってしまう。

(dolist (x load-path)
  (prin1 x)
  (terpri))

こちらは、別解。エレメントに改行を連結した(文字列)ものを、どんどんと累積してくっていう、非効率な方法。

(setq res "")
(dolist (x load-path)
  (setq res (concat res x "\n")))

resに結果が累積されてるので、resC-jで、変数resを表示してみる。

"/home/sakae/.emacs.d/elpa/go-autocomplete-20170626.1023
/home/sakae/.emacs.d/elpa/auto-complete-20170125.245
/home/sakae/.emacs.d/elpa/go-mode-20181012.329
/home/sakae/.emacs.d/elpa/popup-20160709.1429
~/.emacs.d/lisp
/usr/local/share/emacs/26.1/site-lisp
/usr/local/share/emacs/site-lisp
/usr/local/share/emacs/26.1/lisp
/usr/local/share/emacs/26.1/lisp/vc
  :
/usr/local/share/emacs/26.1/lisp/calc
/usr/local/share/emacs/26.1/lisp/obsolete
"

ほー、それぞれのパッケージdirがPATHに加えられ、次にオイラー用のPATHが入り、後は、インストール時のそれぞれが検索対象になるんだな。

golint

前回導入したgolintをedebugで追ってみる。golintを対象にする。

(defun golint ()
  "Run golint on the current file and populate the fix list.
Pressing \"C-x `\" jumps directly to the line in your code which
caused the first message."
  (interactive)
  (compilation-start
   (mapconcat #'shell-quote-argument
=>            (list "golint" (expand-file-name buffer-file-name)) " ")★
   'golint-mode))

カーソルが★印の所に来た時の結果は下記(@mini=buffer)

Result: "golint /home/sakae/go/src/blood/nbld.go"

これでコンパイル動作が行われ、結果がgolint-modeに引き渡されるのかな。

compilation-start is an autoloaded compiled Lisp function in
`compile.el'.

(compilation-start COMMAND &optional MODE NAME-FUNCTION
HIGHLIGHT-REGEXP)

Run compilation command COMMAND (low level interface).
If COMMAND starts with a cd command, that becomes the `default-directory'.
The rest of the arguments are optional; for them, nil means use the default.

MODE is the major mode to set in the compilation buffer.  Mode
may also be t meaning use `compilation-shell-minor-mode' under `comint-mode'.

すいすいとコマンドの説明が引けるってのは、有難い。

mapconcatの第一引数がshell-quote-argumentだけど、これは関数との事。これもhelpで引いて、ソースを参照。#が付いているのはfunctionですって印なのかな。elispって、ひょっとしてlisp-2の系統なのかしらん?

MSDOSがどうたらこうたらと、M$系への配慮っぽい。阿呆なcommand.comを宥めて安全に使えるように気を回しているんだな、ってな事が分かったぞ。