2007年12月07日

日本語を音素に分解してくれるPythonスクリプト

NVDA JPを触っていたら、付属の音声合成eSpeakに日本語がないのはとても残念だと思った。NVDAのポータブル版にそれが入っていれば、USBメモリを差せばどのWindowsXPも、音声パソコンにすることができるから。そういうわけで、いろいろ調べてみた。結局まだできないのだけど、ちょっと面白いものができたので、公開してみることにする。

eSpeakを調べていると、MBROLAという音声合成のプロジェクトを見つけた。世界中の音声が用意されていて、これで使える日本語音声も三種類公開されている。MBROLAのプログラムに音素で表記された文章を与えると、それを音声として読み上げてくれる。これはとても面白い。ということで、漢字仮名交じりの日本語をこれで使える音素に分解するスクリプトを作ってみることにした。

探してみると、既にこのようなプログラムは書かれていて、muDaTTS V0.2という名前で公開されていた。これはRubyというプログラミング言語で記述されていて、OSはlinux向けに書かれていた。これはとてもよくできていて、日本語の文書を渡すとその通りに発音してくれる。ただし抑揚はつけていないので、棒読みになる。

これをNVDAでも使えるようにと思って、NVDAを記述しているPythonというプログラミング言語で書き直してみた。結局まだどう組み込めばいいのかもわからないので、まだまだNVDA対応などではないけれど、とりあえず漢字仮名交じり文を音素に分解できるようになった。出力されるファイルをMBROLAのプレイヤーで開くとちゃんと棒読みで喋ってくれる。muDaTTSは数字の羅列をちゃんと日本語の数として読んでくれる。この再現にはかなり手こずったが、なんとかこちらでも読めるようになった。

基本的な部分はRubyからPythonへの翻訳なので原形をとどめているところもあれば、こんなふうにかなり変更してあるところもある。muDaTTSはアルファベットが読めなかったので、変換辞書を作って対応してみた。単語の日本語での読み方が用意してあればそれで読み、単語が分からないときは綴りを読むようにした。この辞書へは英単語以外も登録できる。muDaTTSでは、漢字から仮名への変換にChaSenというソフトを使っていたが、後発のmeCabというソフトを使ってみたかったので、そちらを使うように書きかえた。またkakasiというプログラムを仮名からローマ字への変換に使っていたが、ここのアルゴリズムを考えるのは面白そうだったので仮名文字から直接音素記号をはき出すように自分で書いてみた。

最終的にはこのスクリプトだけで、しゃべり出してくれることを目的としているけれど、まだMBROLAの扱い方が分からないので、今のところは日本語テキストを標準入力に与えると、音素に分解したテキストファイルを標準出力に吐きだすようになっている。ここで使われている音素の記号は後述するJP2で使われているものである。


とりあえず名前はPyDaTTSとしてみた。ダウンロードは次のリンクからできる。バージョンは0.5。WindowsXPのみ対象。
ダウンロード PyDaTTS.zip

解凍するといろんなファイルが出てくる。

※注意。スクリプトやバッチファイルもファイルの内容を理解してから実行してください。自己責任でご利用ください。ウィルス検査してからUPしてあります。しかし念のため確認してください。

これを動かすためには、四つのソフトがインストールされている必要がある。
・Python 2.5.1 Windows installer
・mecab-win32 0.96
・package (1.8 Mb) for using Mbrola with Windows
・jp2: Japanese Female (5.6Mb) MBROLA用日本語音声JP2

それぞれ以下のサイトのダウンロードページからダウンロードする。リンク先にインストールの指示もあると思うので、それにしたがって入れていけばいい。

Python Programming Language -- Official Website
MeCab: Yet Another Part-of-Speech and Morphological Analyzer
The MBROLA Project
MBROLAのダウンロードページの下の方にJP2も用意されている。JP2を解凍したものをMBROLAがインストールされた場所にコピーする。コピーした中の付属のphoファイルをダウンロードすると、音声登録の指示が出るので、それにしたがって登録すればいい。


それから、今回は使わなかったけれど、muDaTTSでも使われている有名な形態素解析器ChaSen、と漢字かな変換プログラムkakasi
ChaSen -- 形態素解析器
KAKASI - 漢字→かな(ローマ字)変換プログラム



はっきりいって、これはまだプログラムの分からない人には扱えない。これが一般の人向けに利用できるようになるのはまだ当分先だろう。それを作るのは僕である必要もない。

バッチファイルを入れてあるので、「text.txt」に適当な文章を書いて、play.batをダブルクリックすれば、標準的なMBROLAのインストールをしていれば、そしてJP2という日本語音声を入れてあれば、たぶんしゃべり出してくれる。いろいろパラメータをいじるとJP2のサンプルのようにそれなりに抑揚もつけられるのだけど、その数値の意味をまだ理解していないし、たぶん難しいので、棒読み。

今回作ったサンプル音声は以下のリンクから聞くことができる。


本日は、晴天なり。
平成17年10月1日に調査された日本の総人口は、127,767,994人です。


「本日は、晴天なり。」に対して出力されるテキストの内容
_ 300
h 22
o 178
n: 200
dZ 80
i 120
ts 22
u 178
w 22
a 178
_ 80
s 40
e 160
i 200
t 22
e 178
n: 200
n 32
a 168
r 22
i 178
_ 200
_ 300



上記以外でとてもお世話になったページ
ctypes で MeCab 0.9rc6 を使う (2006/01/14)
Zaurusで日本語音声合成 MBROLA+muDaTTS


posted by takayan at 04:01 | Comment(0) | TrackBack(0) | 音声合成 | このブログの読者になる | 更新情報をチェックする

2007年12月12日

WindowsのPython用MeCabパッケージを作って、それに対応してみた。

前回の日本語を音素に分解するスクリプトの公開で、Ctypesを利用してMeCabを操作していたのは、Python用MeCabモジュールのWindows版作れなかったから。せっかく公式モジュールがあるのだから今回はこれを使えるようにしてみた。普通に、MeCabサイトからPython用のバインドモジュールをダウンロードできるが、インストールしようとするとコンパイラとしてVC++2003を要求されてしまう。物持ちがよくて現役を退いたVC++2003を用意できたとしても、今度はコンパイルエラーが出てしまう。

理由を調べてみると、「MeCabのPythonバインディングをWindowsでビルド」に書いてあった。MeCab.hを次の差分のように書き換えなくてはいけない。上記のリンク先の変更とは多少違う。
--- mecab.h.original
+++ mecab.h
@@ -216,12 +216,12 @@

virtual ~Tagger() {}

-#ifndef SIWG
+#ifndef SWIG
static Tagger* create(int argc, char **argv);
static Tagger* create(const char *arg);
-#endif

static const char *version();
+#endif
};

/* factory method */


MeCabサイトに行ってソースを持ってきて解凍し、srcフォルダに入っているMeCab.hを上記のように書き換え、SWIGを使ってmecab_wrap.cxxを作り直す。SWIGというのは、バインドのためのコードを自動で作ってくれるソフト。MeCabの公式のPythonバインドもこれを使って生成されている。SWIGサイトからWindows版を持ってきてインストールするmecabソースのswigフォルダで下記のようなコマンドを実行すると、i拡張子のファイルの指示に従って、仲介するコードを作ってくれる。
swig -python -c++ -o mecab_wrap.cxx mecab.i

なおこのswig.exeにはPATHは通っている。以後上記のようなコマンドの例は適切にPATHなどの環境変数が設定されているとする。

そして、「Windows で MeCab python binding をコンパイルする (2006/01/15)」を参考にして、setup.pyも修正する。MeCabのWindows版が標準の場所「C:/Program Files/MeCab/sdk」にインストールされているとすると、次のようになる。
setup(name = "mecab-python",
version = '0.96',
py_modules=["MeCab"],
ext_modules = [
Extension("_MeCab",
["MeCab_wrap.cxx",],
include_dirs=['C:/Program Files/MeCab/sdk'],
library_dirs=['C:/Program Files/MeCab/sdk'],
libraries=['libmecab'])])


このフォルダで次のコマンドを実行するとセットアップが完了する(VC++2003を持っていれば)。
python setup.py install


さて、VC++2003(VC7.1)が要求されるのはどうしてだろう。それはWindows向けのPython2.5がVC++2003でコンパイルされて作られたプログラムだからだ。もっと詳しく言うとPython2.5がMSVCR71.DLLを使っているから。Windowsではコンパイラの種類によって、それで作ったプログラムの使うMSVCRTの種類が違ってくる。そしてその違うMSVCRTを一つのプログラムの中で使おうとすると、ランタイムエラーでプログラムが止まってしまう。Pythonのセットアップスクリプトは、Python本体を作るときに利用したのと同じVC++2003を要求して、モジュールでも同じMSVCR71.DLLを使おうとする。

VC++2003を持っていると、とりあえず上記の改造されたMeCab.hを使って作ったmecab_wrap.cxxをsetup.pyでコンパイルしインストールができる。では持っていない人はどうすればコンパイルできるだろうか。残念ながらVC++2003は販売終了している。同じランタイムでコンパイルしてくれるものに、Visual C++ Toolkit 2003というコンパイラがある。これは数年前までマイクロソフトから無料で配布されていたコンパイラだ。一緒にPlatform SDKをインストールして使う。そのままではsetup.pyでコンパイルできないが、setupを解釈するスクリプトを改造(Python 2.4 Extensions w/ the MS Toolkit Compiler参照)すれば、パッケージを作ってくれる。残念ながらこのVC++ToolKit2003も配布は終了している。

そういうわけで現在ではMSVCRT7.1を使うマイクロソフト純正コンパイラは入手困難となっている。それでも、どうしても入手しなくてはいけないのならば、マイクロソフト社のMSDNに入会することになるだろう。旧バージョンの使用 (ダウングレード) - MSDN。タイムリーにもMSDNへは現在VC++2008発売直前のキャンペーンで少し安く入会できる。 Visual Studio 2008 早期導入キャンペーン 。もう一つ、特定のバージョンを以前買ったことがある人へのキャンペーンも実施されている。Visual Studio 2008 早期導入キャンペーン 〜今なら更新パッケージがお得!〜。でもどちらにしても、仕事で使わない限り、手軽に買える商品ではないので、これは万人の解決法ではない。

ではどうするかというと、MSVCR71.DLLを使うことを諦めて現在入手可能なコンパイラを使ってMSVCRTそのものを使わないモジュールを作ればいい。その組み合わせで問題が起きないかはやってみないと分からないが。Visual C++ 2005 Express Edition 日本語版(VC8)は登録が必要だが無料で入手できる。これもPlatform SDKをインストールして使う。(Visual C++ 2005 Express Edition と Microsoft Platform SDK を一緒に使うを参照)。このコンパイラでも上で作り直したmecab_wrap.cxxをそのままコンパイルすることができる。そのときコンパイラのスイッチとして、/MDではなく/LDを使う(/MTでも良さそうだが今回/MTで動かなかった)。/LDだとMSVCRTを使わないDLLを作る。とりあえずこれで動く。
cl -EHsc /LD /Ox /I "c:\Program Files\MeCab\sdk"
/I "C:\Python25\include" mecab_wrap.cxx
"C:\Program Files\MeCab\sdk\libmecab.lib"
"C:\Python25\libs\python25.lib"
長いので折り曲げて書いてあるが上記を一行で書く。環境変数path、include、libが適切に設定されているとする。上記「MeCabのPythonバインディングをWindowsでビルド」にあるものと同じコード。

でも、誰かがコンパイルしたものを配ってくれれば個々にこんな面倒なことをしなくてもいい。それもMSVCR71.dllに対応したコンパイル済みのモジュールを。MeCabの配布先でついでに公開してもらえると楽なんだけどまだそれはない。調べてみると、setup.pyスクリプトでモジュールのインストールができると、ほんの少しのコマンドラインの変更で配布用パッケージも作ることができるのが分かった。pyhtonにはこのようにパッケージを作る機能も備わっている。いろんな所で見かけたpythonの配布パッケージもそうやって作っていたのかと今回初めて知った。

長々と書いてきたけれど、そういうわけで、setup.pyを使って作ってみた。自己責任でご利用を。
ダウンロード mecab-python-0.96.win32-py2.5.exe

そして、このMeCabモジュールを利用してスッキリした記述にしたpyDaTTL0.6
ダウンロード pyDaTTL06.zip

上記のプログラムを利用するためには、libmecab.dllへPATHが通っていないといけない。手っ取り早い方法は、"C:/Program Files/Mecab/bin"にあるlibmecab.dllを確実にPATHが通っている"C:/WINDOWS/SYSTEM32/"に放り込めばいい。けれど、MeCabを使っていろいろ試してみるにはこのbinフォルダにある他のコマンドが自由に使えた方が楽なので、今後のためにこのbinフォルダをPATHに追加しておいてもいい。


posted by takayan at 01:20 | Comment(0) | TrackBack(0) | プログラミング | このブログの読者になる | 更新情報をチェックする

2007年12月14日

MinGWでmsvcr71.dllを使う方法

前回は、Windows向けPython2.5で使われているのはmsvcr71.dllだということ。そして現在で回っているマイクロソフト製のコンパイラでは、もう世代が変わってしまっていて、そのmsvcr71.dllにリンクするプログラムを作るのはとても大変だということを書いた。

マイクロソフト製以外にもコンパイラはある。Pythonの向けのパッケージはMinGW GCCを使ってもコンパイルできる。これを使うとmsvcr71にリンクするプログラムを作ることができる。

Python限定の話題ではないが「MinGW GCCでlibcとしてmsvcrt.dll以外を使うまとめ」で詳しくまとめられている。MinGW GCCは標準でmsvcr71.dllではなく、msvcrt.dllへリンクするように作られているので、ちょっと工夫しないといけない。その工夫の仕方が書いてある。

記事の最後の方の「もっと根本的に、specファイルを書き換える方法」で紹介されているspecファイルを書き換えたものを用意して、それをコンパイル時に-specで指定する方法が、一番無難な方法じゃないかと思う。常にmsvcr71.dllへリンクしたものを作るというのならば、「C:/MinGW/lib/gcc/mingw32/3.4.5/(バージョン番号は適宜置き換え)」にあるspecsファイル中の-lmsvcrtを-lmsvcr71に置き換えてしまえば、煩わしくなくなるだろう。

ただ、ためしにmecabソースをその設定でビルドしてみるとctype.hの記述が原因でエラー終了してしまった。すべてがうまくいくというわけでもないみたい。

MinGW GCCではVCとはまた別のライブラリを用意しないといけない。VCのライブラリからの変換の手順はいろんなところで紹介されているので、それにしたがって作ればいい。そもそもMinGW環境そのものの構築の仕方は、面倒なので書いていないが、これも検索すれば見つかる。


posted by takayan at 03:29 | Comment(0) | TrackBack(0) | プログラミング | このブログの読者になる | 更新情報をチェックする

2007年12月17日

MeCabで日本語インタプリタ

MeCabで日本語の構文を自由に解析できるようになったので、それを使って、日本語計算機を作ってみることにした。現在まだ最低限のことができるようになっただけなんだけど、しばらくこれを作っていこうかと思う。これを少しずつ拡張していけば、日本語のプログラミング言語処理系にできるかもしれない。

5年くらい前にプログラミング言語のロゴを日本語化していた。でも日本語の構文解析の部分で挫折してしまった。chasenやMeCabを使えばいいんだろうなということは、あとで分かってきたけれど、なかなか手を出せないまま今に至ってしまった。

それが、最近MeCabをPythonで使ってみて簡単に日本語が解析できてしまったので、これはいけると思ってしまった。それで作ってみることにした。開発言語としては今のところPythonを使ってみる。インタプリタをインタプリタで作るというのは、ちょっと速度的にもったいなさそうだけど、Pythonの気軽なプロトタイピングが今かなり気に入っているので、とりあえずこれで作る。

日本語プログラミング言語というのは古くはMindというものから、現在「なでしこ」「ドリトル」「プロデル」などが出てる。今でも販売されているMindはForthというもともと英単語を後置表記で表記していたプログラミング言語を日本語の単語で使えるようにすることから始まっている。一方の現在主流の日本語プログラミング言語は、ソースを読んで確認したわけではないけれど、どうやらロゴを発展させた形で作られているのではないかと思う。根拠は、以前日本語ロゴを作った経験から。ドリトルの前身はロゴ坊というまさに日本語ロゴだったし、後継ドリトルでも、タートルグラフィックが得意だったりしている。プロデルのプログラムの外見もまさにロゴを日本語語順になおしたものに見える。

さて、すでにこういう先人がいるのだけれど、おそらくそれらを追い越せるだけのものが簡単に作れるとも思わないが、ちょっとアイデアがいろいろ思いつくので、日本語インタプリタを作ってみることにする。それもロゴを手本に作ってみる。MeCabという本格的な日本語解析モジュールを利用するのだから、かなりきっちりとした日本語表記を目指そうと思う。

わざわざダウンロードして試してもらう段階ではないけれど、公開しておきます。

とりあえずできたもの:
■ダウンロード calc.py
稼働条件
・MeCab公式サイトよりMeCabのWindows版をインストールしていること
・Windows版Python2.5をインストールしてあること
・Windows版Python2.5用MeCabモジュールをインストールしてあること

py2exeで実行形式にしたものの圧縮ファイルを置いたつもりが、UPしてませんでした。すみません。


現時点では最低限のことしかできない。
・解釈できるのは一行、それも単文。
・整数の四則演算ができる。
・式の記述は、文章表記。
・現時点利用できる構文は、
「○を□にたす」「○を□にかける」「○から□をひく」「○で□をわる」「○と□をたす」
「○に□をたす」「○に□をかける」「○を□からひく」「○を□でわる」「○と□をかける」
・上記の○、□の場所に数字を入れた文を書いて、エンターを押すと、次の行に答えが出てくる。
・終了と入力すると終了する。

いろいろやることは思いつくけど、まずリスト演算と再帰的な文の呼び出しを実現できるようにしよう。まだ○と□には数値リテラルしか指定できない。これができれば十分複雑なことができるようになる。


posted by takayan at 03:04 | Comment(0) | TrackBack(0) | 日本語プログラミング | このブログの読者になる | 更新情報をチェックする

2007年12月19日

日本語プログラミング言語の仕様を考えてみる

前回作ってみたものをどう発展させようか。以前から考えていたものをまとめてみた。できる限り細かく考えてみたが、整合性がとれるかどうか作ってみないと分からないし、それをプログラムできるかどうか分からない。とにかく考えていることを言葉で表すとこんな感じになる。


■プログラムの構成要素についての定義

文字:
・プログラムの表記を構成する最小単位は文字である。
・文字のコードは16ビットユニコードである。
・エスケープコードによって表現されたものも一つの文字である。
・空白、タブ、改行も文字である。
・空白文字とは空白、タブ、改行である。
・空白文字をエスケープコードで表現したものは空白文字とは見なされない。
・空白文字の連続は一つの空白として扱われる。
・空白は空白文字以外の文字の連続を分割する。

記号:
・記号は一つの文字もしくは複数の文字の連続によって表され、それを構成する文字の列があらかじめ複数定義されている。
・記号は定義されているものの中で最も長いものとしてのみ解釈される。
・記号とそれに隣接する文字や記号とは間に空白が無くても分割されている。

数値:
・先頭から末尾までの文字が数値を表すものとして解釈されるもののみが数値である。
・数値には整数と実数がある。
・整数は小数点を含まない。

ワード(単語):
・分割されていない文字の一つ以上の連続をワードと呼ぶ。
・ワードには属性がある。
・記号は記号という属性をもったワードである。
・整数を表す文字の連続からなるワードは整数という属性をもつ。
・実数を表す文字の連続からなるワードは実数という属性を持つ。
・同一の引用符記号(”’"')に囲まれた文字の連続は、文字列という属性を持つワードである。
・文字列には空白も含めることができる。
・ワードには、言語仕様であらかじめ用意された手順によって値や手順を割り当てることができる。
・ワードに値を割り当てるということは、値を返す手順を割り当てることである。
・ワードは最初それ自身の文字の連続を値として返す手順が割り当てられている。
・数値は常にそれが表す数値を値として持つ。
・文字列は常にそれを構成する文字の並びを値として持つ。

ノード:
・ノードとはワードとリストをまとめて呼ぶための名前である。
・ワードはワードという属性を持ったノードと考えることもできる。
・リストはリストという属性を持ったノードと考えることもできる。

リスト:
・リストはノードによって構成される。
・そのリストを構成するノードを要素と呼ぶ。
・リストは別のリストの要素となるとき、対になるリスト引用符(「」[])で囲まれる。

プログラム:
・プログラムはリストである。
・リストの最初の要素から順に各要素が消費され、実行されていく。


■プログラムの解釈について
・プログラムは文の並びである。
・文は並んでいる順序で解釈される。
・文には単純文と式文がある。
・単純文はいくつかの目的語と述語からなる。
・句点(。)は文を区切る記号であり、それを越えて目的語の探索をしない。
・読点(、)は単なるワードを区切る記号である。
・述語には手順が割り当てられて、それに従って解釈される。
・述語は解釈され値を返すものと値を返さないものがある。
・値を返す述語は必ず値を返す。
・値を返さない述語は必ず値を返さない。
・述語の名前はそれを構成する文字そのものである。
・目的語を持たない単純文もある。
・後置詞は述語とともに定義される。
・述語は後置詞によって目的語の値を受け取り、手順の処理で利用する。
・目的語には後置詞が続いている。
・目的語と後置詞は連結されていてもよい。
・目的語と後置詞の間には空白文字があってもよい。
・後置詞を伴わない目的語も許される。
・後置詞の並びには順序がある。
・同じ名前の述語でも違う後置詞を伴う場合は別のものとみなされる。
・同じ名前の述語で同じ後置詞を伴う場合でも後置詞の順序が違うものは別のものとみなされる。
・目的語の解釈は、述語の解釈に先立っておこなわれる。
・値を返さない目的語があれば、その単純文の解釈は中止され例外が発生する。
・目的語は文である。
・目的語となれるものでもっとも単純なものは、目的語を持たない述語である。
・一つの文章に多くの述語が使われ、目的語の所属の判断が複雑になる場合は、括弧「()()」を使って優先順位を明確にする。
・目的語の探索は再帰的に右側の目的語から決定されていく。
・式文は式によって記述された文であり、必ず値を返す。
・式には単項式と二項式、そしてリスト式がある。
・単項式は単項演算子と項からなり、値を返す。
・二項式は項と二項演算子と項からなり、値を返す。
・演算子には結合順位と結合方向があり、それに従って解釈される。
・演算子は言語仕様に含まれ、新たに定義することはできない。
・演算子は記号であるものと記号でないものがある。
・演算子の項の属性が、演算子が要求するものではないときは例外が発生し、解釈が中止される。
・項は、より結合順位の高い式、もしくは述語のみからなる単純文、もしくは括弧で囲まれている値を返す文である。
・リスト式は、リスト引用符(「」)で囲まれたリストである。
・リスト式の値は、そのリストそのものである。
・述語には名詞形と動詞形がある。
・後置詞の直前には、動詞形の述語は直接置けない。
・動詞形は必要に応じて活用する。
・例外を発生させる述語、およびそれをとらえる述語がある。

将来的にはクラスが扱えるようにしてもいいが、とりあえずそれを考えない。作るとすればこんな感じになるだろう。

クラス(分類):
・インスタンス変数は、「(インスタンス名)の(変数)」で表記する。
・クラス変数は、「(クラス名)の(変数)」で表記する。
・インスタンスメソッドは、「(インスタンス名)を(メソッド)」で表記する。
・クラスメソッドは、「(クラス名)を(メソッド)」で表記する。


そしてこれらの規則に従って基本命令が定義されている。

繰り返し文:
<整数式>回 「<リスト>」を 繰り返す。
「<リスト>」を <整数式>回 繰り返す。
「<リスト>」を <論理式>になるまで 繰り返す。
「<リスト>」を <論理式>の間 繰り返す。
<論理式>になるまで 「<リスト>」を 繰り返す。
<論理式>の間 「<リスト>」を 繰り返す。
「<リスト>」の要素を順に<ワード>とおいて 「<リスト>」を 繰り返す。
「<リスト>」を 「<リスト>」の要素を順に<ワード>とおいて 繰り返す。

判断文:
<論理式>ならば 「<リスト>」を 実行する。
<論理式>でなければ 「<リスト>」を 実行する。
<論理式>ならば 「<リストA>」を、それ以外ならば 「<リストB>」を実行する。
<論理式>ならば 「<リストA>」を、<論理式>ならば 「<リストB>」を、<論理式>ならば 「<リストC>」を、それ以外ならば 「<リストZ>」を実行する。

定義文:
<文>を<ワード>とする。
<文>を<ワード>とおく。
<文>を<ワード>と呼ぶ。
<ワード>に<文>を代入する。
<ワード>←<文>
<ワード>:=<文>

手順の定義:
{<ワード>-<後置詞>} <ワード>の手順 <リスト> 手順終わり


これを一度に実現させることはできないだろうが、暇なときにちょっとずつ作ってみよう。


posted by takayan at 02:53 | Comment(0) | TrackBack(0) | 日本語プログラミング | このブログの読者になる | 更新情報をチェックする

2007年12月20日

やっとキャストチェーン解けた

071220_0126~02.jpg


二年ぐらい前に買ったパズルがやっと解けた。二年間解き続けたわけではないんだけどね。
キャストパズルシリーズで解けなかった最長記録だ。解いてみると、なぜ解けなかったのか不思議でならないくらいあっけないものだ。でもこの達成感は、なにものにもかえがたい。





他にもたくさん



posted by takayan at 01:49 | Comment(0) | TrackBack(0) | 日記・未分類 | このブログの読者になる | 更新情報をチェックする

2007年12月21日

分かりやすい日本語の引数を考える

先日は、箇条書きで"言語"について描写してみた。自分自身で読み返してみて、いろいろ問題点に気がついた。

助詞が使えるというのはとても面白いとは思うのだけど、考えればすぐに、述語の目的語を「て・に・を・は」などの助詞だけで受けられないのはわかる。そういう意味でより拡大した意味を持たせて後置詞と呼ぶことにしたのだけど、さらに考えを進めるとすべての後置詞で受けるというのも無理がある。

例えば、扇形の面積を求める述語を考えてみる。扇形の面積を求めるには、中心角、半径が必要になるが、この中心角と半径にはどんな助詞をつけるべきだろうか。いいものが思いつかない。では助詞に限らず後置詞を添えたらいいだろうか。前回説明はしなかったが後置詞の意味は「後ろに置くワード」という意味で使っている。

すぐに思いつくのが、
「Aという中心角、Rという半径の扇形の面積」

という表現だろう。つまり「という中心角」「という半径の」という後置詞を使う方法だ。でもこれだと後置詞という考えに縛られて、日本語としての自然さを犠牲にしてはいないだろうか。間違った日本語ではいないが、「という」という言葉が頻出することに、くどさを感じてしまう。

数学の教科書ではどんな表現を使っていただろう。たしか「中心角A、半径Rの扇形の面積」ではないだろうか。できればこんな形で表現できた方が僕には美しく感じられる。しかしこれだともう後置詞を使ったいままでの構文とは違うものになってしまう。でもこれが実現できるのなら、構文を壊してでもこっちの方がいい。そう思ってしまう。

これを一般化させると、仮引数への代入を述語の呼び出し文に明記できるということになる。でもよく考えてみると、この実引数は式の可能性だってある。こうなると人間はともかくインタプリタが解析するのが一段と難しくなるだろう。思い通りの解釈が無理になるかもしれない。

ならば実引数の部分を括弧で囲んで明確にすればどうだろう。括弧で囲むというのはいいかもしれない。構文的にも今までの規則を拡張して解決できるだろう。

でも式中だと括弧()は優先順位を変える記号として理解できるが、文章中では補足説明的な役割になってしまう。文章表現を中心にするプログラム言語ならば、式以外での()はあまり使わない方がいい。

では角括弧「」はどうだろう。リストを使うのは構文的に楽に組み込める。でも本来のリストとの区別が問題になってくる。リストとワードでは表されるものが大きく違ってしまう。

仮引数名を括弧の前に置くという特異な構文を使うので、別の記号が使えるのならばそのほうがいい。どうせならば専用の括弧が分かりやすいのではないだろうか。中括弧{}はまだ他のことに使っていないから、それにしよう。

結局こうすることにした。
中心角{A}、半径{R}の扇形の面積

そして、仮引数を明示しなくていいときは、仮引数と波括弧を省略できる。つまり、
AにBを足す

のようにできる。
文の構文を考えると、

= { 仮引数名 '{' 目的語 '}' 後置詞|',' } 仮引数名 '{' 目的語 '}' 後置詞 述語
| { 目的語 後置詞|',' } 目的語 後置詞 述語
| 述語

内部的には反転させて、次のように処理させる。

= 述語 後置詞 '{' 目的語 '}' 仮引数名 { 後置詞|',' '{' 目的語 '}' 仮引数名 }
| 述語 後置詞 目的語 { 後置詞|',' 目的語 }
| 述語


読点'、'はただの区切り記号としていたが、その役割も変える必要がある。このときの読点は並列の記号としてはたらく。つまり、後続する直後の目的語と並列され、そして同じ述語に所属する。その並列している目的語と一緒になって、全体をその後置詞が受けているとみなす。もし後続する目的語の直後に後置詞が無く、かわりに読点がある場合は、同様にして後続の目的語も加えて並列してあるとみなす。必要なだけこれを繰り返す。これができれば自然な文章になるだろう。

「、」に近いが、「と」という演算子も作ろう。これは後置詞のように振る舞う演算子だ。

二項演算子「と」:左右の項をひとつのリストにする演算子。「と」が述語のすぐ左隣の位置にあるときは演算子ではなく、後置詞とみなされる。演算子「と」は記号ではない。したがって「と」に隣接するものは「と」によって分割されることはない。

この二項演算子「と」の存在のために後置詞「と」を右から二番目以降に伴う述語は定義できない。逆に演算子「と」の意味で「と」を使う可能性のある述語は、実引数がリストであるかを判断し、リストであった場合、そのリストの要素を順に処理を行うように手順を記述することになる。

たとえば、「和」という述語がその例になる。この述語は後置詞「の」だけを伴う。実引数がリストの場合は、それらを数値とみなし、その合計を計算する。実引数が数値を表すワードならばそれをそのまま返す。「和」は、自分への引数が演算子「と」によって作られたものであるかは知らない(知れるように細工してもいいが)、そのため演算子「と」を使わずに直にリストの形で引数を与えてもいい。例、「1 2 3 4 5」の和。

今日はここまで。
念のため。これは既存のプログラミング言語を解説したものではなく、作れたらいいなと思っている日本語インタプリタの仕様を夢想する記事。


posted by takayan at 00:57 | Comment(0) | TrackBack(0) | 日本語プログラミング | このブログの読者になる | 更新情報をチェックする

さよなら、デンスケ

デンスケ

デンスケ終了



なんてこった。このブログの右サイドに貼り付けといたデンスケブログパーツが終了していた。昨日までの公開だったらしい。そういうわけで記念のキャプチャ。

消えてしまったはずのものをどうやって撮ったのかというと、タブブラウザを使うとよくこんなことになる。もしやと思って開いているタブリストを確認したら昨日開いたときの元気なデンスケがまだ貼り付いてた。

実をいうと「電脳コイル」は、最後まで見ていない。総復習の少し前から、見ていない。嫌いになったわけでもないのに。


posted by takayan at 02:21 | Comment(0) | TrackBack(0) | 日記・未分類 | このブログの読者になる | 更新情報をチェックする

2007年12月23日

変数の扱いについて

日本語プログラミング言語を妄想する続き。プログラミング言語Logoを日本語化しようという目論みから始まったことだけど、今の仕様ではLogoでの変数の扱いを踏襲していない。できるだけ自然な日本語の表記にしたいからだ。

Logoではワードを値として参照しているときは、頭に「:」を付ける。このlogoの「:」は、「thing "」の省略である。この手続き「thing」は文字列で示された名前の変数に与えられた値を返す手続きである。これをこの言語で実現するとすれば「の値」という述語になるだろう。

「:」でこの省略形を表現することで、logoではそのワードが手続きではなく、変数名として使われていることがはっきりと外見で分かるようにしてある。この用法はとても分かりやすいので、できれば倣ったほうがいいのだけど、日本語で書くときは見栄えがあまり良くない。できるだけ日本語らしく表記することを優先したいので、これを使わないことにする。述語「の値」の用意はするが。

そのため、ワードは次のように処理される。処理速度なんて最初から念頭に置いていない。これは動的な言語である。

■ワードの処理
1.述語として登録されているか調べ、候補を選ぶ。無ければ、3.へ
2.リストの並びを調べ、この候補が矛盾無く使えるか調べる。このとき、複数の述語が適合する場合は、「曖昧な表現です」という例外が発生し、解釈を中止する。一つならば、この述語の結果を持って、5.へ。
3.述語として登録されていないとき、もしくは述語が定義されていてもどれも矛盾してしまうときは、変数としてその名前が登録されているか調べ、登録されていればその値を結果とし、5.へ。
4.変数としても登録されていないときは、「このワードを知りません」という例外を発生させて、解釈を中止する。
5.結果をこのワードの値とする。

■文としてのリストの処理
リストの中のワードは、どのように解釈されるだろうか。答えは、リストはリストのままである。述語に処理されることによってはじめて要素は解釈の対象となる。つまり、制御文など、リストを文として解釈する述語が実行されるときになって、そのリストの内容が文として処理される。実際にはその時点でいわゆるeval関数が実行される。そのとき変数となるべきワードは変数として解釈される。これは手本にしているLogoをそのまま踏襲している。


とりあえずここまで考えておけば、後は作っていくうちになんとかなるだろう。Logo、特にUCBLogoの言語仕様を参考にして、それを日本語の文章を解釈できるようにかなり変形させている。実のところ、考え自体は数年前に漠然とできあがっていたのだけれど、日本語の解析ができずに困っていた。


posted by takayan at 00:38 | Comment(0) | TrackBack(0) | 日本語プログラミング | このブログの読者になる | 更新情報をチェックする

2007年12月27日

日本語で計算するプログラム

先日の「MeCabで日本語インタプリタ」以来文章の中で考えているだけで、プログラムそのものを改良していなかったので、日本語計算インタプリタをちょっと拡張してみた。まだまだなんだけれど、前回より少しまともになった。前回は決められた述語に、数値を入れるだけのことしかできなかったけれど、その数値の所に再帰的に文を使えるようにした。一行に続けて他の文も書けるし、漢数字も解釈できるようにした。

実用的なプログラムではないので、これが人の役に立つというわけでもない。とにかく地味なプログラムだ。ただ前回公開したものよりも、こちらのほうが作りたいものを表現できているので、今回も公開することにする。いままで書いてきた仕様はまだ満たしていないし、ずれている。全体像が見えてくるのはまだまだ先のことになるだろう。

主な操作法を書いておくと;

実行するとDOS窓が立ち上がる。漢字入力が中心なので、ALT+半角/全角キーで切り替える。「たす」「ひく」「わる」「かける」「割った余り」「くっつける」といった言葉を助詞をつけて書いて、エンターを押すと、その答えを表示してくれる。今回から、述語を組み合わせて使えるようになったので、「1と2を足した値に3を足す」とか目的語の部分に既定の述語を使った名詞節をおくことができるようになった。日本語らしく接続するために「〜した値」「〜した答え」「〜した結果」という表現なども使えるようにした。文末の表現としても「〜ます」「〜して、」といった表現もできる。「命令形」もたいてい受け入れてくれる。こういうのはmecabによる解析のおかげだが、単純なことしかやっていないのでもっと工夫すれば正確な表現もできるようになるだろう。思い通りの語形変化を選んでくれないので、mecabの辞書も独自のものを作った方が処理が楽になるだろう。

前の文の結果を参照する言葉として、「その結果」「その答え」などの述語が使える。結果を受けるこの言葉は、同じ行で使ってもいいし、エンターした後、次の行で使ってもいい。なお、計算は整数演算だけしか対応していない。唯一「くっつける」は文字列演算のサンプルだ。

制御構造を文の形で定義できるので、案外単純な仕組みで作れるだろう。きっと一般的なプログラミング言語よりは楽だろう。そういうところはLispの親戚らしい特徴だと思う。お気楽にPythonで作っているのも楽な理由だろうけど。

クラスをもっと徹底的に使って作るべきなのだろうが、今は構造体的なものとしてしか使っていない。どんなデータ構造が必要なのかを実際プログラムを作りながら探している段階なので、全体像が分かってからクラスの中にクラスのメンバになるべき関数を移動させていくつもりだ。それにいくつかのファイルに分割していかないとどんどん分かりにくくなってる。一つのファイルだけで処理系を描ききることができれば、それはそれで面白いかもしれないが。


ためしに次の文章を打ちこむと、

千を十で割った値を三で割ります。その答えに三をかけて、一をその結果に足します。

結果が出力されて、次のようになる。
*** 解析内容 ***
1000 を 10 で 割る を 3 で 割る 。
その答え に 3 を かける
1 を その結果 に 足す 。
****************
*** 翻訳内容 ***
述語: 割る
+述語: 割る
++定数: 1000
++定数: 10
+定数: 3
述語: かける
+述語: その答え
+定数: 3
述語: 足す
+定数: 1
+述語: その結果
****************
結果: 33
結果: 99
結果: 100

出力される内容は、解析内容、翻訳内容、そして結果の順。解析内容はMecabを利用して、このプログラムに必要な分かち書きにした結果。翻訳内容は、これをプログラムに翻訳したもので、データの階層構造が描かれている。そして結果は、これはコマンドラインで入力された「文」の数だけ表示される。結果が三つ出力されるのは、上の文章が三つの文から成り立っていることを示している。目的語として使われていない述語の結果が並んでいる。「その答え」「その結果」という言葉の値は、それぞれここに並んでいる一つ前のものが使われているのがわかる。


次回公開するのは、変数の利用、論理演算、リスト演算、ラムダ式の定義、算術式の解釈など、それなりのものが使えるようになってから、だからたぶん一月以上先になるだろう。


■ダウンロード
無保証。必要な環境は前回と同じ。

Pythonスクリプトソース:
nihongo.py

py2exeで作った実行形式を固めたもの:
nihongo.zip


posted by takayan at 02:26 | Comment(0) | TrackBack(0) | 日本語プログラミング | このブログの読者になる | 更新情報をチェックする