Emacs Lisp 超初心者がコマンドを作ってみる 〜バイトコンパイル〜

ちゃんと動くようになった Emacs Lisp コマンド(のファイル)はバイトコンパイルすると実行速度の向上が期待できます。

python でいうと .pyc みたいなものでしょうか。ただし、 python とは違って自動で作成・更新してくれる訳ではなくて、手動で byte-compile-file 等実行する必要があります。 更新も自動で行われる訳ではないので、 .el を変更したら都度バイトコンパイルするようにしてください。

バイトコンパイルすると拡張子 .elc のファイルができます。

# バイトコンパイルするとワーニングが出る

````
po-ediff-previous-msgid.el:79:6: Warning: the function
    ‘ediff-regions-internal’ is not known to be defined.
````

無視してEmacsを起動すると

````
⛔ Warning (comp): po-ediff-previous-msgid.el:79:6: Warning: the function ‘ediff-regions-internal’ is not known to be defined.
⛔ Warning (comp): po-ediff-previous-msgid.el:44:32: Warning: the function ‘po-previous-untranslated-regions’ is not known to be defined.
⛔ Warning (comp): po-ediff-previous-msgid.el:43:17: Warning: the function ‘po-get-msgid’ is not known to be defined.
⛔ Warning (comp): po-ediff-previous-msgid.el:40:4: Warning: the function ‘po-find-span-of-entry’ is not known to be defined.
⛔ Warning (comp): po-ediff-previous-msgid.el:31:6: Warning: the function ‘po-extract-unquoted’ is not known to be defined.
````

バイトコンパイルした .elc のロード時に
po-mode や ediff の関数を呼び出している部分でワーニングが発生します。

バイトコンパイルしていない .el のロードの場合は発生しません。

調べましたら、 declare-function すると良いようです。 C言語でいう extern 宣言みたいな感じでしょうか。 

C言語の extern 宣言と違うのはどこのファイルにあるかも記述することです(下記 declare-function の "po-mode" とか "ediff" の部分)。

また、引数を、関数を定義しているところからコピって持ってきます。 これまた C言語のプロトタイプ宣言のようなものです。この引数宣言を見て当該関数を使っているシーンで引数の数が合っているかどうかチェックします。

::: note
手元では引数の数合ってない、 と警告が出ました。 原因は decleare-function に記述した引数の数の方が誤っていた訳ですけども、 ともかくも、 ちゃんとチェックしてくれているようです。
:::

declare-function を追加したら、再度バイトコンパイルしましょう。今度はバイトコンパイル時にも、Emacs起動時にもワーニング無しで起動します。

## グローバル変数
グローバル変数の場合は、なぜかdeclareではなくてdefです。 defvarを使って宣言します。グローバル変数の値を変更しない定数扱いの場合は defconst を使うそうです。

# プログラム
declare-function を追記したもの。 .el ファイルの先頭にまとめて書いてしまえばいいようです。

````elisp:po-ediff-previous-msgid.el
;;; po-ediff-previous-msgid.el --- ediff previous-msgid and msgid.
;;; Author: kuma35
;;; Created: 2024/12/18
;;; Commentary:
;; previous msgid is  '#|' marked in comment.
;;; Code:

(declare-function po-extract-unquoted "po-mode" (buffer start end))
(declare-function po-find-span-of-entry "po-mode" ())
(declare-function po-get-msgid "po-mode" ())
(declare-function po-previous-untranslated-regions "po-mode" ())
(declare-function ediff-regions-internal "ediff"
		  (buffer-A beg-A end-A buffer-B beg-B end-B
			    startup-hooks job-name word-mode setup-parameters))

(defcustom po-ediff-previous-msgid-buffer-a-name "*pepm-previous-msgid*"
  "BUFFER A name for `po-ediff-previous-msgid`.  pepm is PoEdiffPreviousMsgid."
  :type 'string
  :require 'po-mode
  :group 'po)

(defcustom po-ediff-previous-msgid-buffer-b-name "*pepm-now-msgid*"
  "BUFFER B name for `po-ediff-previous-msgid` .  pepm is PoEdiffPreviousMsgid."
  :type 'string
  :require 'po-mode
  :group 'po)


(defun po-extract-previous-msgid (buffer start end)
  "Delete '#|' marker and unquote text from BUFFER START END.
delete '^#| ' each line.  then unquote.
return is String with property."
  (require 'po-mode)
  (with-temp-buffer
    (insert-buffer-substring buffer start end)
    (goto-char (point-min))
    (while (re-search-forward "^#\\(~\\)?|[ \t]*" nil t)
      (replace-match "" t t))
    (po-extract-unquoted (current-buffer) (point-min) (point-max))
    )
  )

(defun po-ediff-previous-msgid ()
  "Ediff previous msgid (marked #| ) and msgid."
  (interactive)
  (require 'po-mode)
  (require 'ediff)
  (po-find-span-of-entry)
  (let (
	(oldbuf (current-buffer))
	(msgid (po-get-msgid))
	(untranslated-regions (po-previous-untranslated-regions))
        (beg-A)
	(end-A)
	(beg-B)
	(end-B)
	)
    ;; source buffer for buffer-A
    (save-current-buffer
      (set-buffer (get-buffer-create
		   po-ediff-previous-msgid-buffer-a-name))
      (setq buffer-read-only nil)
      (erase-buffer)
      (dolist (region untranslated-regions)
	(insert (po-extract-previous-msgid oldbuf (car region) (cdr region)))
	)
      (setq beg-A (point-min))
      (setq end-A (point-max))
      (goto-char (point-min))
      (push-mark (point-max) t t)
      (setq buffer-read-only t)
      (restore-buffer-modified-p nil))
    ;; source buffer for buffer-B
    (save-current-buffer
      (set-buffer (get-buffer-create
		   po-ediff-previous-msgid-buffer-b-name))
      (setq buffer-read-only nil)
      (erase-buffer)
      (insert msgid)
      (setq beg-B (point-min))
      (setq end-B (point-max))
      (goto-char (point-min))
      (push-mark (point-max) t t)
      (setq buffer-read-only t)
      (restore-buffer-modified-p nil))
    ;; run ediff
    (ediff-regions-internal
     (get-buffer po-ediff-previous-msgid-buffer-a-name)
     beg-A end-A
     (get-buffer po-ediff-previous-msgid-buffer-b-name)
     beg-B end-B
     nil 'ediff-regions-wordwise 'word-mode nil)
    )  ; end of let
  )

(provide 'po-ediff-previous-msgid)
;;; po-ediff-previous-msgid.el ends here
````
# 注意点
::: note warn
(古い) .elc に注意

ロード時は .el よりも .elc の方が優先されます。

バイトコンパイルあるあるですけども、 .el を更新したら必ずバイトコンパイルも行うか、一旦 .elc を削除してください。そうしないといつまでも(古い) .elc をロードしてきて「修正したハズなのに反映されない。なんでだろ？」と悩む事になります…
:::

詳しくは

Emacs Lisp マニュアルの

> 16 ロード

こちらさんを参考にさせてもらってます

https://ayatakesi.github.io/lispref/29.4/elisp-ja.html#Load-Suffixes