A Riddle Wrapped in a Mystery

中身はエニグマ・Emacs

数学用アルファベットをEmacsで入力する。

Emacs Lisp の math-symbols.el は、通常の文字をイタリック文字やフラク トゥール文字へ変換できる。

Unicode の数学文字

通常、イタリックや太字は文字符号ではなくフォントで区別するが、数式で使 う文字のイタリックや太字は意味的に異なるため、別の文字符号でこれらを扱う。

Unicode で符号化されているスタイルは以下のとおりである。

styles / scripts alphabets greeks* numerals
bold yes yes yes
(bold) italic yes yes yes
(bold) fraktur yes no no
(bold) script yes no no
double-struck yes partial yes
monospace yes no yes
sans-serif (italic) yes no yes
sans-serif bold (italic) yes yes yes
subscript partial no yes
superscript partial no yes

上の表において、アルファベットとは、大文字・小文字それぞれ26文字ずつ である。また、ギリシャ文字は全て数学用ギリシャ文字記号とナブラ(ϵ, ϑ, ϰ, ϕ, ϱ, ϖ, ∇)を含む。

Unicode の符号とフォント

Unicode では、数学用文字の符号は U+1D400 からの SMP面で符号化されてい る。ただし、一部の早期にBMPで符号化されていた文字 (ℜなど) は別である。

数学記号フォントは STIX Font Project が開発したフォントが主要なOSにはす でにインストール済である。

init.el の設定

単に (require 'math-sybols) でインストールされる。

文字のスタイル化

アルファベット等の文字をスタイル化したい場合は、領域を選択して、 M-x math-symbols-XXXX-region を実行する。たとえば abcde を 太字イタリック にしたい場合は、 M-x math-symbols-bold-italic-region とすれば良い。

math-symbols-fraktur-region abcde 𝔞𝔟𝔠𝔡𝔢
math-symbols-bold-region abcde 𝐚𝐛𝐜𝐝𝐞
math-symbols-italic-region abcde 𝑎𝑏𝑐𝑑𝑒
math-symbols-script-region abcde 𝒶𝒷𝒸𝒹ℯ
math-symbols-double-struck-region abcde 𝕒𝕓𝕔𝕕𝕖
math-symbols-sans-serif-region abcde 𝖺𝖻𝖼𝖽𝖾
math-symbols-superscript-region 12345 ¹²³⁴⁵

スタイル化した文字をもとに戻すには M-x ucs-normalize-NFKC-region を実行 する。文字列を変換するには、 (math-symbols-italic-region "hogehoge") と実行する。文字列中に変換できない文字がある場合(空白含む)はエラーに なるが、 C-u プレフィックスで抑制できる。

(math-symbols-fraktur-string "fghijk") ⏎
"𝔣𝔤𝔥𝔦𝔧𝔨"

Emacs Lookup で Goo のWeb辞書を利用する。

Lookup の ndweb エージェントクラスは、世界中にあるWeb上の辞書を利用できる。

ndwebのオプション

Lookup でWeb辞書を使用する場合は、以下のオプションを指定する。

:self
OpenSearch Description URL
:title
辞書のタイトル
:methods
辞書の検索メソッド(前方一致など)
:suggestions
候補の選択方法 (関数ならば HTMLファイルから候補を生成、URLならば OpenSearch JSON を指定する)
:results
選択した候補の内容を表示する URL。
:method
HTTP Post または Get のどちらを利用するか。

なお、:self が指定されているのならば、:title, :suggestions :results :methods 等のオプションの指定は必要ない。 これらは :self で指定された OpenSearch XML から自動的に取得される。逆に、:self がない場合は最低限、:title, と :results が必須になる。

Mycroft Project では、全世界 2万以上の辞書の検索エンジンの OpenSearch XML が蓄積されている。ndweb エージェントの位置に、”mycroft:XXX” を指定 すると、自動的に Mycroft のサイトからXXX の OpenSearch XML を取得して、 辞書を設定する。

ndweb-options

ndweb-options には、goo等のようないくつかのnewebエージェント用の独自オ プションがプリセットされている。ndweb-options にあるWebサイトを使えば、 自分でオプションを設定する必要なく、すぐにそのWeb辞書を利用できる。

設定

ndweb-options.el を参考に、使用したいWeb辞書のURLの ”http://” を除いた部分を指定する。

(setq lookup-search-agents 
      '(...
        (ndweb "dictionary.goo.ne.jp/jc")
        ...))

2013-03-21-goo.png

Emacs Lookup で Sony DD-CH10 の日中辞書を利用する。

Lookup は、かつて Sony が発売していた電子辞書 DD-CH10 CD-ROMに入っていた小学館の日中・中日辞書が利用できる。 この辞書は語彙・例文が充実していて、発売後15年が経過した今でも人気が高い。

EBXA-C

DD-CH10 は、電子辞書規格 EBXA を中国語向けに拡張した、これ一台限りの特別規格 EBXA-C を採用している。 この辞書の閲覧には、EBXA-C 対応のパッチを当てた eblook を利用する。

漢字インデクサ

DD-CH10 の日中辞書は平仮名とピン音でしか検索できないが、EB漢字インデクサ を使用することで、漢字でも検索できるようになる。

ピン音フィルタ

Lookup の support-zhongri ファイルは、中国語の文字をピン音に直すクエリフィルタが組み込まれている。 このクエリフィルタが使用する関数 lookup-text-hanzi-to-pinyin は、漢字をピン音に変換する。 漢字は台湾漢字・中国漢字の両方を使用できる。複数の発音がある漢字は全ての組み合わせを返す。

(lookup-text-hanzi-to-pinyin "重慶") ⏎
("zhong4qing4" "chong2qing4")

設定

DD-CH10 のCD-ROMをコピーして、ndeb エージェントとしてディレクトリを指定する。

(setq lookup-search-agents 
      '(...
        (ndeb "~/edicts/ZHONG_RI")
        ...))

2013-03-19-ddch10.png

Emacs で Zsh の ヒストリファイルを閲覧・編集する

zsh のヒストリファイル /.zsh_history は、ASCII以外の文字に対してエス ケープをしているため、通常はエディタで閲覧したり編集できない。

Emacsで、 zsh_history 独自のデコーダ・エンコーダを実装することで、 zshヒストリファイルの閲覧・編集できるようにする。

zsh ヒストリファイルの構造

zshのヒストリファイルは、保存するデータの各バイトにおいて、それが 0x80-0x9d または 0xa0 のとき、その前に 0x83 を入れて、続くバイトの 6bit 目を反転させるようになっている。詳細は zsh のソースコードの meta.c を参照。

zsh ヒストリファイルのデコーダ

以下に zshのヒストリファイルのデコーダをCCLで示す。ただし、本来はCCLは UCS符号を出力することになっているが、このCCLデコーダはUTF-8のバイトシー ケンスを出力する。(UTF-8のデコーダを別途接続する必要がある。)

(define-ccl-program zsh-history-decoder
  '(1 ((loop
        (read-if (r0 == #x83)
                 ((read r0) (r0 ^= #x20)))
        (write r0)
        (repeat))))
  "decode .zsh_history file.")

zsh ヒストリファイルのエンコーダ

(define-ccl-program zsh-history-encoder
  '(2 ((loop
        (read r0)
        (r1 = (r0 < #x9e))
        (r2 = (r0 == #xa0))
        (if (((r0 > #x82) & r1) | r2)
            ((write #x83) (write (r0 ^ #x20)))
          (write r0))
        (repeat))))
  "encode .zsh_history file.")

zsh ヒストリファイルの符号体系

上記のデコーダ・エンコーダは、UTF-8 バイト列を対象としているが、これら はEmacsではLatin-1に解釈されてしまう。そのため、一旦Latin-1と解釈された 符号をUCS符号に直さなければならない。そのために、post-read-converter と、 pre-write-converter を用意する。

(defun post-read-decode-utf8 (len)
  (save-excursion
    (save-restriction
      (narrow-to-region (point) (+ (point) len))
      (encode-coding-region (point-min) (point-max) 'latin-1)
      (decode-coding-region (point-min) (point-max) 'utf-8)
      (- (point-max) (point-min)))))

(defun pre-write-encode-utf8 (_ignored _ignored2)
  (encode-coding-region (point-min) (point-max) 'utf-8)
  (decode-coding-region (point-min) (point-max) 'latin-1))

上記をまとめて、zsh-history という符号化体系を作成し、これを zsh_history という文字列を含むファイルに結び付ける。

(define-coding-system 'zsh-history "ZSH history"
  :coding-type 'ccl
  :charset-list '(unicode)
  :mnemonic ?Z :ascii-compatible-p t
  :eol-type 'unix
  :ccl-decoder 'zsh-history-decoder
  :post-read-conversion 'post-read-decode-utf8
  :ccl-encoder 'zsh-history-encoder
  :pre-write-conversion 'pre-write-encode-utf8)

(modify-coding-system-alist 'file "zsh_history" 'zsh-history)

なお、Emacsは保存する際に最適な符号化を方法を見つけて再割当てを行うため、 一旦、この符号化方式でファイルを保存した後は再度、ファイルを読みなおす 必要がある。

Emacs Lookup で PDICタイ語辞書を使う

Lookup は、PDICの タイ語辞書 を扱うことができる。

PDIC タイ語辞書

日・タイ辞書、タイ・日辞書(かな)、タイ日辞書(ローマ字)の3種類がある。 漢字からは検索できないので、漢字語を検索したい場合はフィルタを設定する必要がある。

漢字・ひらがなフィルタ

Lookup の lookup-query-filter-kanji-to-kana は、クエリの文字を漢字から 平仮名に変換する。この関数は内部で mecab を使う lookup-text-get-kana-readings を呼び出している。

(lookup-text-get-kana-readings "山本山") ⏎
("やまもとやま" "やまもとさん")

設定

エージェントにオプションとして :query-filter lookup-query-filter-kanji-to-kana を設定する。ただし、サポートファイル support-pdic-thai.el がすでにこの設定をしているため、辞書IDに “Uni-PdicThai-JP-IPA” という文字列が含まれているならば、ユーザが何か設 定する必要はない。

(setq lookup-search-agents 
      '(...
        (ndpdic "~/edicts/PdicThai")
        ...))

2013-03-16-thai.png

Emacsで字通を読む

字通 は白川静の辞書3部作の最高峰。書籍版は重いし価格も23,000円と高いが、 CD-ROM版なら安いしHDDにコピーできてノートパソコンで持ち歩ける。

EmacsのLookup で字通CD-ROMを読むための検索エージェント ndjitsuu を開発した。

インデックスファイルの作成

「字通」を検索するためには、インデックスファイルを事前に作成する必要が ある。lookup のmisc フォルダに入っている jitsuu_mk_jukugo_list.rb と~jitsuumkoyajilist.rb~ を使って、インデックスファイルを作成する。

1
2
3
% cd ~/edicts/Jitsuu/DATA/
% ruby jitsuu_mk_oyaji_list.rb > oyaji.txt
% ruby jitsuu_mk_jukugo_list.rb > jukugo.txt

次に、生成された oyaji.txt と jukugo.txt の外字をUCS漢字コードに置換す る。

M-x find-file oyaji.txt
M-x ndjitsuu-convert-gaiji-to-ucs
M-x save-buffer

M-x find-file jukugo.txt
M-x ndjitsuu-convert-gaiji-to-ucs
M-x save-buffer

oyaji.txt と jukugo.txt は DATAフォルダと同じ位置に置く。

(setq lookup-search-agents
      `(...
        (ndjitsuu "~/edicts/Jitsuu")
        ...))

lookup-search-agents 変数に ndjitsuu を加えて、 M-x restart すれ ば字通をEmacsで読めるようになる。

image:/images/2013-03-15-jitsuu.png

Emacs Lookup で Karak PDIC中日辞書を使う

Emacs Lookup で、Karak氏 が作成した中日辞書 を使うことができる。

PDIC形式辞書

PDIC は Unicode にフル対応した辞書フォーマットで、日本の多言語辞書によ く利用されている。特徴として、内部文字符号として BOCU (Binary Ordered Compression of Unicode) と呼ばれる圧縮形式を採用している。

BOCUはハフマン符号ほど圧縮率は大きくはないが、圧縮後の文字列が辞書式順 序を維持しているため、辞書には適した圧縮方式である。

BOCUデコーダ

Lookup に付属している bocu.el は、BOCU符号をデコードする。 bocu.el は、レジスタベースのCCL言語で記述されていて、Emacs Lisp に比べておよそ 5~10倍程度高速に動作する。

ndpdicエージェント

ndpdicエージェントは、 bocu.el を利用して、PDIC辞書の検索と表示を行う。 CJ2辞書で利用するには以下のように設定する。

1: (setq lookup-search-agents
2:       '(....
3:         (ndpdic "~/edicts/cj2")
4:         ...

ndpdicエージェントは、PDIC辞書が入ったフォルダを指定すると、そこから拡 張子 ”.dic” のファイルを探し、辞書オブジェクトを生成する。

M-x lookup-restart で中日辞書が現れるかを確認し、検索する。

2013-03-15-cj2.png

Emacsの汎変数

Emacs 24.3 で導入された汎変数を使えば、ソースコードの可読性が高まる可能 性がある。

setf の例

汎変数の代入マクロ setf を使えば、 car だけで setcar 相当が記述できる。

(setq a (cons 3 5))
(setf (car a) 10)
a
→ (10 . 5)

setcar/setcdr という関数を別に覚えるよりは、学習コストは低い。同様に、

(setq a (list 1 10 100 1000 10000))
(elt a 3)
→ 1000
(setf (elt a 3) 300)
a
→ (1 10 100 300 10000)

となる。

setf に対応している関数

24.3 で、setf に対応している関数は以下の通り。setf を使えば右の関数は 不要になる。覚える関数が減れば、プログラム作成・保守も楽になる。

汎変数の対象 対応する設定関数
   
aref aset
car setcar
cdr setcdr
caar setcar/car
cadr setcar/cdr
cdar setcdr/car
cddr setcdr/cdr
elt setcar/nthcdr
get put
gethash puthash
nth nthcdr/setcar
nthcdr nthcdr/setcdr
default-value set-default
frame-parameter set-frame-parameter
terminal-parameter set-terminal-parameter
keymap-parent set-keymap-parent
match-data set-match-data
overlay-get overlay-put
overlay-start move-overlay
overlay-end move-overlay
process-buffer set-process-buffer
process-filter set-process-filter
process-sentinel set-process-sentinel
process-get process-put
window-buffer set-window-buffer
window-display-table set-window-display-table
window-dedicated-p set-window-dedicated-p
window-hscroll set-window-hscroll
window-parameter set-window-parameter
window-point set-window-point
window-start set-window-start
progn  
let, let*  
cond  
if  
cons  
logand  

汎変数を使うと、構造の構築子とアクセス関数のペアだけで、構造に式を代入 できる。たとえば hash の場合、これまではmake-hash-table, gethash の他に puthash が必要だったが、setf を使えばputhash は不要になる。

(puthash key val table) ≅ (setf (gethash key table) val)

汎変数の作り方

gv.el の最後には、コメントアウトされている alist-get 関数の実装例があり、 自分の汎変数を作る際の参考になる。

 1: (defun alist-get (key alist)
 2:   "Get the value associated to KEY in ALIST."
 3:   (declare
 4:    (gv-expander
 5:     (lambda (do)
 6:       (macroexp-let2 macroexp-copyable-p k key
 7:         (gv-letplace (getter setter) alist
 8:           (macroexp-let2 nil p `(assoc ,k ,getter)
 9:             (funcall do `(cdr ,p)
10:                      (lambda (v)
11:                        `(if ,p (setcdr ,p ,v)
12:                           ,(funcall setter
13:                                     `(cons (cons ,k ,v) ,getter)))))))))))
14:           (cdr (assoc key alist)))

上記は一見複雑だが、重要なのは以下の3点である。

  • 汎変数にしたい関数は、関数定義内で (declare (gv-expander (lambda (do) …))) を宣言するか、または関数定義外で (put 'func 'gv-expander (lambda (do arg1 arg2 …) …)) で汎変数関数を設定する。 [fn:: ~declare~ 経由で汎変数関数を設定する場合、 ~do~ 以外の引数の追 加は ~gv–defun-declaration~ が自動的に行なってくれる。]
  • 汎変数関数は、関数 do と 元関数の引数を引数にとる。そして汎変数関 数の中では do の引数の形で、 setter 処理を行う式を出力するコード と getter 処理を行う式を出力するコードを記述する。上記の例では getter`(cdr ,p) で、 setter(lambda (v) `(if (….getter))) となる。その際、 setter の中に getter を記述して も構わない。
  • (gv-letplace (setter getter) place body) は、上記のように書かれた汎 変数関数に対して、 place リストの最初の要素の関数に対応する汎変数関 数の getter 関数と setter 関数を取り出して、それぞれをシンボル setter getter に束縛する。

この方式は、かなり洗練されていて面白い。Emacsの汎変数がこのような形で設 計されているのは以下の理由による。

  • setter プログラムと getter プログラムを同一関数内で記述することで、 構文スコープ下で両機能で変数を共有でき(クロージャ)、互いにネストし たり副作用を与えられる自由度の高いコードがかける。
  • マクロで記述しているので高速化ができる。特にEmacsの汎変数の根幹であ る gv-get マクロは事実上コンパイラになっている。

この do にどのような関数を入れるのかは、アプリケーションによって異な る。たとえば setf は、 do 関数として、 setter/getter 両引数のうち setter のみを呼び出して汎変数に引数の式を入れる。また gv-letplace は、 do として引数の settergetter をそのまま (setter getter) に紐づける lambda 関数を使っている。

上記の例である alist-get は、cl.el の cl-assoc と同じ機能を持つ関 数である。 gv-expander で設定される関数は、関数 do, 位置 key, 構 造 alist を引数とする。

(macroexp-let2 macroexp-copyable-p k key …) により、引数 key がコピーされて、 k に代入される。

(gv-letplace (setter getter) alist …) は、 alist の最初の要素( 当然 alist-get となる)に対応する汎変数関数(すなわち自分自身) に対して、その値を操作する2関数 gettersetter (すなわち自分自 身の gettersetter プログラム)を取り出す。 getter は現在の key での alist-get の値を返し、 setter は引数の値を alist に入 れる。

(macroexp-let2 nil p `(assoc ,k ,getter) … ) で、 palistk 対応値(Consセル)が入る。

setf が実行されると、 setf 内の do 関数は2つめのの引数の setter を使って値を代入し、結果として得られる式を評価する。

以下は実行例。

(setq a '(("a" . "b") ("c" . "d")))
(alist-get "a" a) → "b"
(setf (alist-get "a" a) "q")
(alist-get "a" a) → "b""q"

Emacsの汎変数を記述する gv.el は、コンパイラやモナドに通じる興味深いコー ドとなっている。

Footnotes:

1

declare 経由で汎変数関数を設定する場合、 do 以外の引数の追 加は gv–defun-declaration が自動的に行なってくれる。

Dired のソート順のカスタマイズ

Dired におけるファイル名の順番

Emacs の ファイルブラウザ dired では、ファイルの並び順を “s” キーで切り 替える。

切り替え順をアルファベット順・日付順の他にサイズ順やファイルの種類順で 切り替えられるようにする。

GNU ls のインストール

Mac に標準で付属する “ls” 命令は、ファイルを種類順で並び替えることがで きない (-Xオプション)。MacPorts 等で、GNU ls をインストールする。

% sudo port install coreutils +with_default_names

オプション +with_default_names を付けておかないと、ls命令は “gls” 命 令としてインストールされる。glsの方が良い場合は init.el に以下のように 書く。

(when (executable-find "gls")
  (setq insert-directory-program "gls"))

dired の sort命令の拡張

以下に、 dired-toggle-sort を拡張した、 dired-rotate-sort を示す。 この命令で、 dired-mode-map の “s” キーバインドを置き換える。これで “s”キーを押すたびに、diredのファイルの並び順を アルファベット順・更新 時間順・拡張子順・サイズ順に切り替わる。

;; dired の sort を拡張する。
(defvar dired-sort-order '("" "t" "S" "X")
  "-t (時間) -X (拡張子) -S (サイズ) なし (アルファベット順) を切り替える。")
(defvar dired-sort-order-position 0)

(defun dired-rotate-sort ()
  "Rotate dired toggle sorting order by `dired-sort-order'"
  (interactive)
  (setq dired-sort-order-position
        (% (1+ dired-sort-order-position) (length dired-sort-order)))
  (setq dired-actual-switches
        (concat dired-listing-switches (elt dired-sort-order
                                          dired-sort-order-position)))
  (dired-sort-other dired-actual-switches))
(define-key dired-mode-map "s" 'dired-rotate-sort)