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

オレオレ言語を作成したけど，「Emacs 用のモードができたら使うわ」とか言われたそこの貴方！

generic.el と langdoc.el を使えば，オレオレ言語用のモードもヘルプ用のモードも簡単に作成できちゃうよ！
今回は，適当に作った [brainfuck-mode](https://github.com/tom-tan/brainfuck-mode/) の中身を説明しながら，言語モードとヘルプモードの作成方法を紹介します．

この記事読んだらできること

* オレオレ言語モードの作成方法 (キーワードに色とかがつく)
* ヘルプ用のモードを定義して，関数等の詳細なヘルプを別バッファで見せる方法 (オレオレ言語モードで describe-function 的なことができる)
* eldoc を利用したヘルプの簡易表示方法 (minibuffer に関数名とかのヘルプを表示できる)

# オレオレ言語モードの作成
Emacs 標準で使える `define-generic-mode` があれば，言語モードの作成は簡単に出来ます．
こんな感じ．

```el:
(require 'generic)
(define-generic-mode brainfuck-mode
  nil
  nil
  ;; 第4引数には，色分けして欲しい単語に当てはまる正規表現と，色分け用の face を書く
  '(("[^]><+.,[-]+" . font-lock-comment-face)
    ("\\]\\|\\["    . font-lock-keyword-face))
  ;; 第５引数には，brainfuck-mode を有効にするファイルに当てはまる正規表現を書く
  '("\\.bf\\'")
  ;; 第６引数に，brainfuck-mode の有効時に実行する関数を書く
  '(define-bf-keymap bf-help-doc-fun)
  ;; モードの説明
  "Major mode for brainfuck")
```

第６引数の `define-bf-keymap` ではキーバインドを定義しています．
定義は以下です． `define-key` の行を追加していくことで，キーバインドを追加できます．

```el:
(defvar brainfuck-mode-local-map nil "Keymap for brainfuck-mode")

(defun define-bf-keymap ()
  (setq brainfuck-mode-local-map (make-keymap))
  (define-key brainfuck-mode-local-map
      "\C-cf" 'bf-help-describe-symbol)
  (use-local-map brainfuck-mode-local-map))
```

`bf-help-doc-fun` は，brainfuck の各コマンドのヘルプを minibuffer に表示するため準備を行う関数です．
この関数の定義は，ヘルプ用のモードの定義が終わってから行うのがいいでしょう．

# ヘルプ用のモードの定義
## 準備: langdoc.el のインストール
MELPA を package.el のリポジトリに登録していれば，
`M-x package-install langdoc` でインストール出来ます．

## ヘルプ用のモードの定義
オレオレヘルプモードの作成のため，関数を2つ用意します．

* 引数を取らず，カーソルが指している単語を文字列で返す関数．brainfuck-mode では以下のような関数を定義しています．普通の言語だと `current-word` を使えば多分何とかなります．

```el:
(defun pointed-symbol ()
  (buffer-substring-no-properties (point) (1+ (point))))
```

* `pointed-symbol`が取ってきた文字列を引数にとって，そのドキュメントを文字列で返す関数．
brainfuck-mode では以下のような定義になっています．

```el:
(defun lookup-doc (sym)
  "SYM の説明を表示する．文章は Wikipedia の Brainfuck の項を転載([, ] のみ一部改変)した．"
  (pcase sym
    (">" "ポインタをインクリメントする。ポインタをptrとすると、C言語の「ptr++;」に相当する。")
    ("<" "ポインタをデクリメントする。C言語の「ptr--;」に相当。")
    ("+" "ポインタが指す値をインクリメントする。C言語の「(*ptr)++;」に相当。")
    ("-" "ポインタが指す値をデクリメントする。C言語の「(*ptr)--;」に相当。")
    ("." "ポインタが指す値を出力に書き出す。C言語の「putchar(*ptr);」に相当。")
    ("," "入力から1バイト読み込んで、ポインタが指す先に代入する。")
    ("[" "ポインタが指す値が0なら、対応する `]' の直後にジャンプする。")
    ("]" "ポインタが指す値が0でないなら、対応する `[' （の直後）にジャンプする。")))
```

最後に，`langdoc-define-help-mode` を呼んで，Help モード (bf-help-mode) とそれを brainfuck-mode から参照する関数 (bf-help-describe-symbol)を自動生成します．

```el:
(langdoc-define-help-mode 
                          ;; ヘルプモード名とヘルプ表示関数の名前のプレフィクス
                          bf-help
                          "Major mode for brainfuck help"
                          "*Brainfuck Help*"
                          ;; カーソル上の文字列を返す関数
                          'pointed-symbol
                          ;; describe-symbol 的な関数の補完に使う
                          '(">" "<" "+" "-" "." "," "[" "]")
                          ;; 関数名からヘルプ用の文字列を返す関数
                          'lookup-doc
                          ;; 第7引数以降はおまけ．
                          "`\\([^']+\\)'"
                          (lambda (a b) b) (lambda (a b) b)
                          "`" "'")
```

第７引数以降に
* リンク化する文字列を表す正規表現
* 上にマッチした部分文字列を受け取って，リンク化された文字列を返す関数
* 上にマッチした部分文字列を受け取って，リンク先を返す関数
* リンク前につける飾り
* リンク後につける飾り
を入れておくと，別のコマンドのヘルプにリンクを張ったりできます．

# eldoc との連携
カーソル位置の単語のヘルプを文字列で返すインタラクティブ関数を定義します．
上で定義した `pointed-symbol` と `lookup-doc` を使えば簡単に書けると思います．

```el:
(defun bf-help-minibuffer-string ()
  (interactive)
  (let* ((sym (pointed-symbol)))
    (when sym (lookup-doc sym))))
```

最後に以下のように，eldoc との連携のセットアップを行う`bf-help-doc-fun`を定義すれば完成です．

```el:
(defun bf-help-doc-fun ()
  (make-local-variable 'eldoc-documentation-function)
  (setq eldoc-documentation-function
        'bf-help-minibuffer-string))
```

# できあがり！
これでどんなオレオレ言語でもそれっぽくヘルプ用のモードを作成出来ますね！
どんどんオレオレ言語 with オレオレ言語モード作って公開するといいよ！