Mg

前回TECOをやった時、emacsの起源なんて題目でWebを調べていたんだ。そしたら、たまたま 行き着いたページにemacsのクローンなんてのが紹介されてた。

Mgとか言うのがOpenBSDではデフォで採用されてて、最初から入っているそうな。 長年(でもないけど)OpenBSDを使ってきたけど、そんなの知らなかったぞ。本当に入って いるのか? 疑い深いオイラーは早速調べてみたのであります。

起動するとemacsそっくりさんが顔を出した。特徴的なステータスラインだけお眼にかける。

-----Mg: *scratch*                        (fundamental)--L1-------------------

manは有るか?

NAME
     mg ? emacs-like text editor

SYNOPSIS
     mg [-n] [-f mode] [+number] [file ...]

DESCRIPTION
     mg is intended to be a small, fast, and portable editor for people who
     can't (or don't want to) run emacs for one reason or another, or are not
     familiar with the vi(1) editor.  It is compatible with emacs because
     there shouldn't be any reason to learn more editor types than emacs or
     vi(1).

ひょっとして、誰かが知らない間に入れてくれたのかな? 親分が認定してるなら、 決定的な証拠が有るはず。そう、ソースツリーに登録されてるか?

[ob: ~]$ ls /usr/src/usr.bin/mg
Makefile     cscope.c     funmap.c     macro.c      random.c     ttydef.h
README       def.h        funmap.h     macro.h      re_search.c  ttyio.c
autoexec.c   dir.c        grep.c       main.c       region.c     ttykbd.c
basic.c      dired.c      help.c       match.c      search.c     tutorial
bell.c       display.c    kbd.c        mg.1         spawn.c      undo.c
buffer.c     echo.c       kbd.h        modes.c      sysdef.h     version.c
chrdef.h     extend.c     key.h        obj@         tags.c       window.c
cinfo.c      file.c       keymap.c     paragraph.c  theo.c       word.c
cmode.c      fileio.c     line.c       pathnames.h  tty.c        yank.c

これで決定的になったな。サイズは170kバイトと小ぶりだよ。DebanのデフォEditorである nanoってどれぐらいだろう? ああ、デフォのEditorと言ったらviだな。こちらはサイズが 344kも有った。emacsは大きい(8.0M)、重いってのは嘘だな。

まてまて、クローンですから、まがいものですから、どこかで手抜きが有るに違いありません。 どこで手抜きしてるか、暴いてみろ。それはやぶさかではありませんが、親分の監視の目が 光ってる所では、おおっぴらにはできませんですがな。

だったら、NetBSDなりLinuxでやってみれ。そうすな、最近保障期間が切れた、15.04版のウブ ででもやってみますか。保障が切れたんで、煮ようが焼こうが好きに振る舞えば。。。。

でも、どこにソース有る? そんなのportsに聞いてみれ!あそこには原料の入手先やらが 書いてあるから。

ああ、READMEに面白い事が書いてあった。

                                                   You may never have
to learn another editor.  (But probably will, at least long enough to
port Mg...)  Mg was formerly named MicroGnuEmacs, the name change was
done at the request of Richard Stallman.

portsの更新

portsは、tar玉になったやつを取ってきて、展開しただけっていう放置ぶり。無保守状態。 これじゃいかん、年も改まった事だし、最新に更新しておこう。

FreeBSDだと、cd /usr/ports; sudo make update だけで済んじゃうだけど、OpenBSDとかは どうやるの? OpenBSD を current にする によると、

export CVSROOT="anoncvs@anoncvs.jp.openbsd.org:/cvs"
cd /usr/ports
cvs -d$CVSROOT up -Pd

NetBSDだとChapter 2. どこからpkgsrcを得て、どうやって最新に保つか に、説明が有った。よかったね。

所で、FreeBSDでは2万数千のportsが登録されてるけど、OpenBSDでは幾つぐらい有るんだろう? ちょいと調べてみる。

[ob: ~]$ find /usr/ports -type d -maxdepth 2 | wc -l
    6569

何でも有りと違って、厳選してるのかな? それとも、、、、。NetBSDはどうだ?

nb7$ find /usr/pkgsrc  -type d -maxdepth 2 | wc -l
   14122

mgは何処に?

mgは、こちらだそうです。

NetBSDのpkgsrcには、似たような名前でmg2aなんてのも有るなあ。mgと友達? mg2a/DESCRを参照すると、NetBSD用にちょいと装飾を施しましたなんて事が書かれていたよ。

あれ、mgと似た名前でngなんてのも有るぞ。それにしても凄い名前だ。

Ng(Nihongo Micro Gnu Emacs) is a Mg(Micro Gnu Emacs)'s
japanese port. Ng supports EUC, JIS and SJIS code.
Ng also have (rather simple) C-mode.

It is also very useful even if you don't need Japanese support.

上で挙げたnanoも有った。Picoってのが原型でGNUが実装しなおしてnanoになったとな。 nanoはPicoに対してライセンス的な問題は有りませんと謳ってる。 RMSはicro Gnu Emacsって名前に異議を唱えてmgに改名させたって商標権を主張したけど、 自分は注意深いのね。そう言えば、ルックアンドフィールがそっくりな事には異議を 唱えなかったのは大人な対応だなあ。

それに対して、アプルと韓国某は、不毛な戦いを繰り広げているように思うのは、 オイラーだけ? どちらが勝つかで売れ行きや株価に直結するから、大らかには いかないのね。

上に出てきた、ピコ、ナノ、マイクロって単位の接頭語だな。まだ、フェムトとかミリには お眼にかかっていない。大きい方は、キロ、メガ、ギガ、テラ、ペタとかが有るな。誰か、editor の命名に迷ったら採用してみれ。新たな元素を作って、命名権を手に入れる事よりずっと簡単だぞ。 言った者勝ちですから。

mg をウブに突っ込んでみる

上記サイトからtar玉を取ってきた。configureなんてお馴染みのものは入っていない。その代わりに、 README_PORTINGなんてのが置いてあった。早速読んでみると、libbsdを入れて、リナちゃんに BSDの真似をしてもらおうって寸法みたい。OpenBSD親分の拡販政策だな。

下記のように歴史のある製品だけど、ちょいと入れられるようにしておかないと広められないからね。 そう言えば、昔はLinuxも簡単にコンパイル出来たけど、今はどうなの?みんなバイナリーで 提供されちゃって敷居が下がるのはいいけど、作る楽しみが奪われている気がするぞ。

* Nov 16, 1986: First release to mod.sources
* Mar 3, 1987: First Release (mg1a) via comp.sources.unix
* May 26, 1988: Second release: (mg2a) via comp.sources.misc
* Jan 26, 1992: Linux port released by Charles Hedrick. This version
  later makes its way onto tsx-11, Infomagic, and various other Linux
  repositories.
* Feb 25, 2000: First import into the OpenBSD tree, where it is
  currently maintained with contributions from many others.

Linuxって1992年にはメジャーになってたの? 多分emacsを動かすのが 難しかったのが、mgがもてはやされたのかな。わーい、おいらのlinuxでもemacsが動いて いるよ。凄いだろーってな具合で、普通の人を騙すのはちょろいものだったのでしょう。 (多分、きっと。)

BSD系にはMakefileが、Linux系にはGNUmakefileが用意されてるので、make一発で入る。 インストールはしないで、解体の材料にする。manが読めたらいいな。man manしたら、 こうすると、ローカルの原稿を読めるって書いてあった。

sakae@uB:~/src/mg-20150323$ man -l mg.1

で、manをつらつらと眺めている。デフォのキーバインドが説明されてた。簡易版のチュートリアルも 有るんで、emacsの復習にはうってつけ。知らないコマンドと言うかキーバインドが有ったりして、 奥が深い事を改めて感じる。

どんなコマンドが有るかなとかキーバインドはどうだっけってのは、mgに聞くのが正解。 M-x help すると a b c: って聞いてくる。aはaprops、bはkeyのbinding、cは指定した キーの関数対応。

manしてて、cscopeなんてのが眼についたので、aproposしてみた。

cscope-create-list-of-files-to-index
cscope-find-called-functions
cscope-find-egrep-pattern       C-c s e
cscope-find-files-including-fileC-c s i
cscope-find-functions-calling-this-functionC-c s c
cscope-find-global-definition   C-c s d
cscope-find-this-file           C-c s f
cscope-find-this-symbol         C-c s s
cscope-find-this-text-string    C-c s t
cscope-next-file
cscope-next-symbol              C-c s n
cscope-prev-file
cscope-prev-symbol              C-c s p

何やら、cscopeに力が入っているようだ。親分がemacsやるならcscopeは常識でしょって、 必須習得を要求してんだな。素直に従ってみるか。

cscopeとは

etags作ってemacsでソースの中をすいすい飛び回るやつです。emacsは何でも有りでunixの 流儀にそぐわない。

C語のソースをすいすい見る為に開発されたのがcscope。manの一行説明では

NAME
       cscope - interactively examine a C program

見たいソース軍のある所へ移動して、cscopeするだけで、すいすい閲覧出来る。起動すると 画面の下側に


Find this C symbol:
Find this global definition:
Find functions called by this function:
Find functions calling this function:
Find this text string:
Change this text string:
Find this egrep pattern:
Find this file:
Find files #including this file:
Find assignments to this symbol:

こんな質問項目が出て来る。質問したい項目まで、Ctl-n, Ctl-p もしくは、矢印キーで 移動して、入力すればよい。結果は、上側に番号が振られたファイル名で示されるので、 その番号を指定。viがリードモードで起動する。って、vi知ってる前提だな。

上の結果欄と下の入力欄との切り替えはTABキーが担当。すいすい飛び回れて便利。 なお、cscopeの終了は、Ctl-d ってのは、一部の人にはお約束事項だろう。 そして、すいすい飛び回る為のデータベースを初回の起動時にcscope.outって名前で 自動作成してる。サブdirとかが有る場合は、cscope -R すると良い。

ちょいとネットを探ってみると、

cscopeでlinuxカーネルソースを読む

Emacsからcscopeソースコードナビゲータを使ってみよう

emacsから使う人も居るみたい。cscopeでカーネルを読む人が居るって事は、それなりに 便利なんでしょう。mgにも組み込んだから、使ってみれ、だな。

mg 解体

例によって、何処で止まっているか?

(gdb) bt
#0  0xb7fdbbe0 in __kernel_vsyscall ()
#1  0xb7ea5733 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:81
#2  0x0805fd40 in ttgetc () at ttyio.c:182
#3  0x08056d38 in getkey (flag=1) at kbd.c:99
#4  0x08056ef9 in doin () at kbd.c:162
#5  0x080594fb in main (argc=0, argv=0xbffff0c8) at main.c:188

gdbで追いかけるのもいいけど、今回はソースをザッピングしてみる。名前から想像するんだぞ。 気ままに見ていてもしょうがないので、少し目標を立てておく。

1.それぞれのユーザーコマンドの呼び出し方法。2.キーバインドはどうやってるか?3.cscopeとの やり取りはどうやってるか? ぐらいかな。

眼についたのが、funmap.c

/*      $OpenBSD: funmap.c,v 1.49 2015/03/19 21:22:15 bcallah Exp $     */

/*
 * If the function is NULL, it must be listed with the
 * same name in the map_table.
 */

struct funmap {
        PF               fn_funct;
        const            char *fn_name;
        struct funmap   *fn_next;
};

static struct funmap *funs;

static struct funmap functnames[] = {
        {apropos_command, "apropos",},
        {toggleaudiblebell, "audible-bell",},
        {auto_execute, "auto-execute",},
          :
        {cssymbol, "cscope-find-this-symbol",},
        {csfindtext, "cscope-find-this-text-string",},
        {csnextfile, "cscope-next-file",},
           :
        {yank, "yank",},
        {NULL, NULL,}
};

しっかり、OpenBSDの刻印が押されていますなあ。次に眼が行ったのは、keymap.cっていう、 いかにもってやつ。

/*
 * initial keymap declarations, deepest first
 */

static PF cHcG[] = {
        ctrlg,                  /* ^G */
        help_help               /* ^H */
};

static PF cHa[] = {
        apropos_command,        /* a */
        wallchart,              /* b */
        desckey                 /* c */
};

static PF cCsc[] = {
        cscallerfuncs,          /* c */
        csdefinition,           /* d */
        csegrep,                /* e */
        csfindfile,             /* f */
        rescan,                 /* g */
          :
        cssymbol,               /* s */
        csfindtext              /* t */
};

こんなのが幾つも登録されてた。なんとなく、分かったような分からないような。。。 おっ、cscope.cってもろな、名前のファイルが有るなあ。内容はcscopeの通信みたい。 man cscopeするとヒントが得られるかな?

ここからは、動的に追って行く。大雑把な流れとして、main.c/mainの中で

175        for (;;) {
:
184                update(CMODE);
 :
188                switch (doin()) {
189                case TRUE:
190                        break;
191                case ABORT:
192                        ewprintf("Quit");
193                        /* FALLTHRU */
194                case FALSE:
195                default:
196                        macrodef = FALSE;
197                }
198        }

184行目で、コマンド実行結果の画面への反映。188行目で、キーボード入力、解析、実行って 事みたい。

doinにBPを置いて、tutorialファイルを読み込み、そこからM-f してみた。 kbd.cの中のdoin。

159=>      curmap = curbp->b_modes[curbp->b_nmodes]->p_map;
160        key.k_count = 0;
161        while ((funct = doscan(curmap, (key.k_chars[key.k_count++] =
162            getkey(TRUE)), &curmap)) == NULL)
163                /* nothing */;
164
165        if (macrodef && macrocount < MAXMACRO)
166                macro[macrocount++].m_funct = funct;
167
168        return (mgwrap(funct, 0, 1));
169}

何か、大事そうなcurbpから必要なものをcurmapに取り出しているな。大本データはいかに?

(gdb) p curbp
$35 = (struct buffer *) 0x808aff8
(gdb) p *$
$36 = {
  :
  b_modes = {0x8075178 <fundamental_mode>, 0x0, 0x0, 0x0},
  :
  b_fname = "/home/sakae/src/mg-20150323/tutorial", '\000' <repeats 987 times>,
  :
  b_fi = {
    fi_uid = 1000,
    fi_gid = 1000,
    fi_mode = 33188,
    fi_mtime = {
      tv_sec = 1415991727,
      tv_nsec = 0
    }
  },
  :
  b_lines = 342
}

どうやら、読みこんだファイルに対しての情報のようだ。そして、160行めに達した時、

(gdb) p curmap
$37 = (KEYMAP *) 0x80750c0 <fundmap>
(gdb) p *$
$38 = {
  map_num = 8,
  map_max = 8,
  map_default = 0x80576a0 <selfinsert>,
  map_element = {{
      k_base = 0,
      k_num = 7,
      k_funcp = 0x8075000 <fund_at>,
      k_prefmap = 0x8074c60 <ccmap>
    }}
}

ファイルの種類に応じて、使えるkeymapが抽出されるんだな。 と、だらだら見て行ってもきりがないので、初心に返って、ヘッダーファイルを覗く。 見るのは、def.h だって、一番太っちょなんだもん。

struct buffer {
        struct list      b_list;        /* buffer list pointer           */
        struct buffer   *b_altb;        /* Link to alternate buffer      */
        struct line     *b_dotp;        /* Link to "." line structure    */
        struct line     *b_markp;       /* ditto for mark                */
        struct line     *b_headp;       /* Link to the header line       */
        struct maps_s   *b_modes[PBMODES]; /* buffer modes               */
        int              b_doto;        /* Offset of "." in above line   */
        int              b_marko;       /* ditto for the "mark"          */
        short            b_nmodes;      /* number of non-fundamental modes */
        char             b_nwnd;        /* Count of windows on buffer    */
        char             b_flag;        /* Flags                         */
        char             b_fname[NFILEN]; /* File name                   */
        char             b_cwd[NFILEN]; /* working directory             */
        struct fileinfo  b_fi;          /* File attributes               */
        struct undoq     b_undo;        /* Undo actions list             */
        struct undo_rec *b_undoptr;
        int              b_dotline;     /* Line number of dot */
        int              b_markline;    /* Line number of mark */
        int              b_lines;       /* Number of lines in file      */
};

これ、編集してるファイルに対応してるんだな。そして、表示してるバッファーに対応する 構造体 mgwin も定義されてた。

一方、キーマップは、bufferの中でつかっているけど、その定義はkbd.hに有った。

struct map_element {
        KCHAR            k_base;        /* first key in element          */
        KCHAR            k_num;         /* last key in element           */
        PF              *k_funcp;       /* pointer to array of pointers  */
                                        /* to functions                  */
        struct keymap_s *k_prefmap;     /* keymap of ONLY prefix key in  */
                                        /* element                       */
};

#define KEYMAPE(NUM)    {                                               \
        short   map_num;                        /* elements used */     \
        short   map_max;                        /* elements allocated */\
        PF      map_default;                    /* default function */  \
        struct map_element map_element[NUM];    /* really [e_max] */    \
}
typedef struct keymap_s KEYMAPE(1) KEYMAP;

struct maps_s {
        KEYMAP          *p_map;
        const char      *p_name;
        struct maps_s   *p_next;
};

ややこしい定義だな。こういうのは図に描くと理解が早まるかな。 長くなってきたので、この辺で。次の回に続く(かも)。