簡易英和辞書コマンドを作成し、Vim から利用する

最近、Vim で英語のドキュメント(ソースコード中のコメント含む)を読む機会が増えました。カーソルの下にある単語の意味をぱぱっと引けるような英和辞書の機能が欲しいと思い、適当に(読解のヒントがなんか出ればいいや程度のノリで)作ってみました。

肝心のところで grep 呼んだりしてますので、UNIX環境での動作が前提です。


注記:コメント欄で早速突っ込まれているのですが、Kaoriya版vim には dicwim.vim という、そのものずばりなプラグインが添付されています。なので、このエントリでやっていることはまったくもって車輪の再発明なのですが、そもそもをこれを自作した経緯として、

  • 自分はオリジナルの Vim を使っている
  • dicwin.vim は単体では配布されていないっぽい
  • 添付プラグイン欲しさに別の Vim本体をダウンロードとか激しく嫌

というのがありますのでご容赦下さい。

追記:関連エントリが(無駄に)3つに分散していたため、このエントリにまとめました。[2], [3] は削除。プログラムは最終的に完成したもののみを掲載します。(2010-01-05)

1、英和辞書ファイルの調達

英単語57,350語収録の英和辞典 GENE95 を英和辞書データとして利用します。

% wget 'http://www.namazu.org/~tsuchiya/sdic/data/gene95.tar.gz'
% tar -zxvf gene95.tar.gz
% nkf --guess gene.txt
Shift_JIS

辞書ファイルは Shift-JIS でエンコードされています。UTF-8 な環境で利用するので、UTF-8 に変換し、どこか適当な場所(自作ライブラリ用ディレクトリなど)に配置します。(変換の際、改行コードも LF に変更しておきます*1

% nkf -Sw -Lu -m0 --in-place gene.txt
% mv gene.txt ~/my/lib

2、辞書引きプログラムの作成

紆余曲折あって現在のバージョンは以下のようになっています。*2(最終更新:2010-01-07)

#!/usr/bin/env ruby
# encoding: utf-8
#
# Name        : edict
# Description : English-Japanese dictionary program powered by GENE95
# Version     : 0.0.8 (2010-01-07)
# Author      : h1mesuke
# 
# Copyright (c) 2009-2010 h1mesuke
# Licensed under the same terms as Ruby. No warranty is provided.
#
Usage = "edict [-v] word"

suppress_last_newline = 
  ARGV.reject! {|v| v =~ /\A-v\Z/ } ? true : false

unless ARGV.length >= 1
  puts "usage: #{Usage}"
  exit 1
end

module EDict

  # GENE95 辞書
  # http://www.namazu.org/~tsuchiya/sdic/data/gene.html
  #
  DICT = "~/path/to/gene.txt"

  def search(word)
    catch(:found) do
      grep(word) if word =~ /[A-Z]/

      word = word.downcase
      grep(word)

      [ # *s
        [/ies\Z/, 'y'],
        [/ves\Z/, 'fe'],
        [/ves\Z/, 'f'],
        [/es\Z/, ''], 
        [/s\Z/, ''], 
        # *ing
        [/cking\Z/, 'c'], 
        [/([^aiueo])\1ing\Z/, '\1'],
        [/ing\Z/, ''], 
        [/ing\Z/, 'e'], 
        # *ed
        [/cked\Z/, 'c'], 
        [/ied\Z/, 'y'],
        [/([^aiueo])\1ed\Z/, '\1'],
        [/ed\Z/, ''], 
        [/ed\Z/, 'e'], 
        # *er, *est
        [/ie(r|st)\Z/, 'y'],
        [/([^aiueo])\1e(r|st)\Z/, '\1'],
        [/e(r|st)\Z/, ''], 
        [/(r|st)\Z/, ''],
        # *ly
        [/ly\Z/, ''], 

      ].each do |pat, rep|
        w = word.dup
        w.sub!(pat, rep) && grep(w)
      end

      [word, "not found\n"]
    end
  end

  private
  def grep(word)
    grep_out = `cat #{DICT} | grep -A1 '^#{word}$'`.lines.to_a
    unless grep_out.empty?
      meaning = grep_out.last
      throw :found, [word, meaning]
    end
  end
end

include EDict
word = ARGV.shift.strip
result = search(word).join(": ")
result.chomp! if suppress_last_newline

print result

# vim: filetype=ruby

辞書引きの結果は最終的には Vim の :echo コマンドを使って出力します。検索に際しては、複数形を単数形に戻す、三人称の s を取り除く、現在進行形、過去形、形容詞の比較級、最上級を原形に戻す、といったことを順次行ってマッチを試行していきます。不規則変化形についても、多くの場合 GENE95 の方に見出し登録されていますので、大体対応できます。

参考文献:

単体で動かすと以下のようになります。

名詞

固有名詞

% edict Japan
【国名】日本

% edict japan
漆器,漆(を〜に塗る)

複数形 → 単数形

% edict boxes
box: 詰め所,箱

% edict enemies
enemy: かたき,敵,敵国,敵軍

% edict echoes
echo: こだま,反響させる,反響する

% edict knives
knives: ナイフ
動詞

三人称単数 → 原形

% edict stops
stop: 止める,止まる,中断する,ストップ,滞在,止まること,停留所,停止

% edict passes
pass: 1.通行(許可)証,通過,合格,パス,手品,2.山道,峠,〜越え,関門,3.形勢,事態,危機

% edict studies
study: 1.〜を勉強[研究]する,学ぶ,調べる,2.調査,勉強,3.書斎,勉強する

現在進行形 → 原形

% edict having
have: 持っている,経験する,わかる,受ける,食べる,にも〜する

% edict admitting
admit: 収容できる,認める,(入ることを)許す,承認する,受け入れる

過去形 → 原形

% edict helped
help: 役に立つもの,雇人,助力,手伝う,促進する,役立つ,救う,助け,救済法,お手伝いさん,手を貸す,助ける

% edict agreed
agree: 同感である,一致する,性に合う,承諾する,同意する,気が合う,賛成する

% edict applied
applied: 適合された,応用の,実用的な

% edict stopped
stop: 止める,止まる,中断する,ストップ,滞在,止まること,停留所,停止

過去形(不規則)
不規則動詞の過去形は GENE95 にて見出し登録されており、それにマッチします。

% edict brought
brought: 連れてきた,持ってきた,もたらした

% edict built
built: 1.buildの過去・過去分詞形,2.《会話》がっちりした造りの,体格のよい,3.組み立ての,寄せ木の

% edict broke
broke: 1.breakの過去形,2.<動物が>飼い慣らされた,調教された,3.一文なしの,金欠の,破産した
形容詞

比較級、最上級 → 原形

% edict faster
fast: しっかりと,高速の,ぐっすりと,身持ちが悪い

% edict fastest
fast: しっかりと,高速の,ぐっすりと,身持ちが悪い

% edict hotter
hot: [t air]暑い,熱い,辛い,激しい,怒った,はらはらする,元気はつらつ,エネルギッシュな,活気に満ちた,うまい,上手な,新しい,最近

% edict hottest
hot: [t air]暑い,熱い,辛い,激しい,怒った,はらはらする,元気はつらつ,エネルギッシュな,活気に満ちた,うまい,上手な,新しい,最近

% edict happier
happy: 満足して,幸福な,適切な,うれしい,幸運な,うれしく思う,最高の

% edict happiest
happy: 満足して,幸福な,適切な,うれしい,幸運な,うれしく思う,最高の
ChangeLog

0.0.8_2010-01-07

  • ビジュアルモードまたはコマンドラインモードから空白を含む見出しを検索できるようにした

0.0.7_2010-01-05

  • 検索メソッドをモジュールにカプセル化
  • 出力末尾の改行文字を取り除くオプション(-v)を追加
    これにより、出力が折り返しなしで1行に収まる場合は復帰のための ENTER が不要に

Pure Ruby版は?

肝心の検索部分が外部プログラム呼出しなのもなんだかなーと思い、辞書ファイルから生成したハッシュをマーシャリングして保存し、実行時にそれを読む込むバージョンも作ってみたのですが、5倍くらい遅くなりました。純粋な検索だけなら一回一回 grep でファイルをスキャンするより遥かに高速なはずなのですが、マーシャリングしたデータ(3MB程度)からハッシュ(要素数約57350)を構築するコストが大きすぎて話になりませんでした。ハッシュを構築した状態で常駐するような形態のプログラムにしない限りは、grep呼出し方式を速度において凌ぐことはできないと思います。

3、辞書引きプログラムの呼出し

上に揚げた辞書引きプログラムを呼び出し、結果を :echo する Vim のユーザー定義コマンドを作成し、これを簡単に実行するためのキーマップを設定します。割り当てキーには適当にあいているものを使います。

変更:出力末尾の改行文字を削除する -v オプション*3を追加、指定するようにした。(2010-01-05)

command! -nargs=1 EDict :echo system("edict -v '<args>'")

nnoremap <C-K> :EDict <C-R><C-W><CR>
nnoremap ,<C-K> :EDict <C-R><C-W>

vnoremap <C-K> "wy:EDict <C-R>w<CR>
vnoremap ,<C-K> "wy:EDict <C-R>w

では、テスト

Dictionary にカーソルをおいて、<割り当てキー> を押下!

うまくいきました。

検索したい語の切り出しがうまくいかない場合はもうひとつのキー割り当て({rhs} 末尾の がないバージョン)を使用すれば、

:EDict <カーソル下の単語>

カーソル下の単語がコマンドラインに入った状態でコマンドラインモードに入るので、単語を編集の上 を押します。*4

*1:前のバージョンでは改行コードが CRLF のままの辞書ファイルを使っていたため、マッチに使う正規表現がおかしなことになっていた(汗)

*2:辞書ファイルを同梱の上 tarball にして配布するとか、gem にするとか考えてみたのですが、めんどくさいのでやめました (^^;

*3:一応 vim の v です。optparse を使うべきかとも思いましたが、スイッチ一つに optparse も大袈裟なので却下。

*4:現状、カーソルの下に単語がない状態でこれを呼び出すと「カーソルの位置には文字列がありません」といわれてしまい、コマンドラインモードに入れません。関数にしてごにょごにょすればよさそうだけど vimスクリプトはよくわからないのでここまで。