【Rubyプログラミング狂室】
Ruby/Tkに挑戦・(疑似)ハナモゲラ和歌・疾風怒濤編
(2002.03.31)

解説

 いくつかコードを書き散らし、なんとなく要領が判ってきた感じがす るので、いよいよRuby/Tkに挑む。

 知る限り、Ruby/Tkの文献は少ない。千数百円を投ずるのが厭わしく ない人で、かつ256倍文体に免疫のある人は、『Rubyを256倍使うための 本・界道編』を買うのもいいだろう。ぼくもこれを参考にした。

まず、tanshi.rbを直す

 実際の作業とはちょっと前後するが、立志編 で作ったtanshi.rbに手を入れる。あれでは「日本の短詩」の基底 クラスだけを定義していたが、短歌や俳句も含めることにする。蟲が見 つかったのでそれも直した。

 tanshi.rbに短歌や俳句のクラスを入れて実験したら、短歌の筈なのに 五七五が出てくる。どうも、フォーマットを保持するクラス変数 @@fomroがいけないようだ。ものの本を見ると「クラス変数はサ ブクラスで新しく作られることはなく、スーパークラスの変数が使われ まっす」と書いてある。なるほど。最後に定義したクラスのフォーマッ トが短詩クラス一族全体のフォーマットになってしまったんだな。

 では、定数を使うべきなのか、「クラスのインスタンス変数」を使う か、クラスインスタンスのインスタンス変数か? 試行錯誤の末定数を 使うことにしたが、そうすると今度は短歌も俳句も何も生成されない。 基底クラスTanshiで空の配列にしているのが災いしているらしい。どー すりゃいいんだ?

 ということで、次のようになった。

コード

  1: # tanshi.rb -- rafinado de programo generanta japanajn mallongajn 
  2: #		poemojn
  3: #
  4: 
  5: require 'jcode'
  6: $KCODE = "sjis"
  7: 
  8: # 短歌、俳句など日本の短詩のスーパークラス
  9: class Tanshi
 11:   Cand  = "あいうえおかきくけこさしすせそたちつてとなにぬねの" + 
 12:     "はひふへほまみむんもやゆよらりるれろわをん" +
 13:     "がぎぐげござじずぜぞだぢづでどばびぶべぼぱぴぷぺぽゐゑ" 
 14: 
 15:   Formo = []	# その詩の連ごとの語数の配列(俳句なら5, 7, 5)
 16: 
 17:   # 内部メソッド: 次の一文字を選びとる
 18:   def sekva()
 19:     nth = rand(Cand.jlength) * 2
 20:     ## DEBUG printf "deb %d %s in %d\n", nth, s[nth..nth+1], s.jlength
 21:     Cand[nth..nth+1]
 22:   end
 23: 
 24:   # 短詩をひとつ捻り出す。引数はその詩型のフォーマット(韻律)
 25:   def generi(formo)
 26:     uta = ""
 27:     for n in formo do   # nは第x連の文字数を表す
 28:       1.upto(n){|m|	# 悪いけどmに用はない
 29: 	uta << sekva()
 31:       }
 32:       uta << " "	# 連の区切り
 33:     end
 34:     uta.sub(/ $/, "")	# 末尾の全角空白を取り除く
 35:   end
 36: end
 37: 
 38: # 短歌クラス
 39: class Tanka < Tanshi
 30:   Formo = [5, 7, 5, 7, 7]
 41:   # generiを再定義し、親クラスのgeneriをフォーマットを与えて呼び
 42:   # 出す
 43:   def generi
 44:     super(Formo)
 45:   end
 46: end
 47: 
 48: # 俳句クラス
 49: class Haiku < Tanshi
 50:   Formo = [5, 7, 5]
 51:   # 短歌と同様
 52:   def generi
 53:     super(Formo)
 54:   end
 55: end
 56: 
 57: # end of file

 generi自体は親クラスの手続きを呼び出して済ませたいのだが、 Tanshi#generiの中で参照しているフォーマットFormoは親クラスのもの が使われる(Rubyは静的なスコープを持つので、考えてみればこれは当 然だ)。そこで、Tanshi#generiに引数としてフォーマットをとるよう にし、サブクラスではgeneriを再定義した。

 考えてみれば、それならフォーマットは定数として持つ必要もなく、

class Tanka
  def generi
    super([5, 7, 5, 7, 7])
  end
end

だっていいことになる。この方がよいかも知れないかも知れないかも。

 それにしても再定義というのはなんだか気持が悪い(どーしてだろ う?)。こんな風にしたっていい筈である。

class Tanshi
  def initialize(formo)
    @formo = formo
  end
  def generi
    for n in @formo
    ...
  end
end

# 短歌はインスタンスだ
tanka = Tanshi.new([5, 7, 5, 7, 7])
tanka.generi
...

 このコードは机上の作品で未実験だけれど、仮にこれでうまくいくと しても、これはまた気分が悪い。「和歌」というのはやはりひとつの類 型であろう。だからクラスとして定義したい。問題は「各詩型のフォー マットを各サブクラスで保持しつつ、親クラスのメソッドgeneriで参照 するにはどうするか」という点なのだが……でも深く考え込むのはやめ ておこうっと。

Tkのお世話になる

 これまでRuby/Tkでちょっとしたお遊び程度のものは書いたが、具体 的に何かするものを書くのは初めてで(ハナモゲラ和歌もお遊びなんだ が)、どうするものかしばらく考えていたわけで。

 それにしても、昔むかしはGUIプログラミングといえばおいそれとは できないものだった。リソースエディタで部品を作ったり(マッキントッ シュのResEditで遊んだのを思い出すな)、それをCプログラムから操っ たり、それはもう大騒ぎだった。それが今やスクリプトで気楽に書くこ とができる。それ自体はすごい進歩であり、天国である(GUIにうっか り手を出すと大変な目に遭うので、その意味では地獄もまた身近になっ たのだが)。

 というわけだから、気楽に書いてみよう。

 Tcl/Tkのバージョンによっては、以下のプログラムは動かなかったり 変な振舞いをする可能性がある。ぼくはバージョン8.3.4というのを使っ ている。

こんなの

  1: #! c:/loka/ruby/bin/ruby
  2: 
  3: require 'tk'            # Tkを使うときのお約束
  4: require 'tanshi'        # わがハナモゲラ短詩クラス
  5: 
  6: tanka = Tanka.new       # 短歌オブジェクトを作る
  7: 
  8: font = TkFont.new(['mincho', 12])       # (1)
  9: 
 10: txt = TkText.new(nil, 'font' => font,   # (2)
 11: 		 'width' => 40,
 12: 		 'height' => 12
 13: 		 ).pack
 14: 
 15: TkButton.new(nil, 'text' => '詠む'){
 16:   command(
 17: 	  proc{                         # (3)
 18: 	    isshu = tanka.generi
 19: 	    isshu << "\n\n"
 20: 	    txt.insert("end", isshu)    # (4)
 21: 	  })
 22:   pack
 23: }
 24: TkButton.new(nil, 'text' => 'もう止める',
 25: 	     'command' => proc{TkRoot.new.destroy}).pack
 26: 
 27: Tk.mainloop     # (5)

説明

 試行錯誤というよりは勉強と実験を繰り返して辿り着いたのが上のコー ド。

  1. フォントオブジェクトを作る。これは後でフォント指定をする際に 使い回せる。
  2. テキストウィジェットを用意する。
    幅(width)、高さ(height)は指定フォントの文字サイズを単位とする ようである。つまりこの例では40文字分の幅で12行。
    詠まれた短歌を表示するのにこのテキストを使う。表示ができさえ すればよいのだから、最初は「ラベル」ウィジェットでいいかとか、 「メッセージ」の方がいいかとか思ったりしたが、ちょっと試した 末、テキストにすることにした。
    packメソッドで(これはTcl/Tkのpackコマンドに相当する)ウィン ドウの中に適宜配置され、表示される。
  3. 「詠む」ボタンが押された時の振舞いをcommand手続きの中に記述 することになる。Tcl/Tkならばここは当然Tcl/Tkのコマンドになる のだが、Ruby/Tk ではRubyのスクリプトを書く。手続きオブジェク トとして記述することになるようである。
    先に作っておいた短歌さんに一首詠んでいただく。
  4. 短歌さんが詠んでくださった歌を、テキストウィジェットに挿入す る。insertの使い方はTcl/Tk本を検索して独力で見つけた。
  5. GUIプログラムを走らせるためのおまじない。これでイベントルー プが動き出す。

 なお、「もう止める」ボタンを押したときに、TkRoot.new.destroy と、TkRootにdestroyメッセージを送っている。単純に exitでもいい筈なのだが、ぼくの今の環境(MS-Windows Me + tcsh) では具合が悪くなることがあるようなので(こういう曖昧な言い方をし なければならないところがシアトルものの奥ゆかしいところ)このよう にした。

 それから、ふたつのボタン(「詠む」と「もう止める」)で、 command を書く場所が違っている。「詠む」ではnewメソッドに渡すブ ロックの中で、「もう止める」ではnewメソッドの引数として、それぞ れ書いているが、効果はどちらも同じだそうだ。

面白がってあれこれやってみる

 使い方はなんとなく判ったので、一気にステップアップを図る。

  1. 短歌を表示する領域にスクロールバーをつける。
  2. ボタンの配置を(少しは)見栄えよくする。
  3. ファイルへの書き出しができるようにする。
  4. となれば、メニューバーとメニューが欲しくなる。どうせならマッ キントッシュのガイドラインに沿って「ファイル」「編集」のメ ニューをつけよう。

 上に挙げたことは大概タネ本に出ているのでそんなに悩むことはない。 でも本に出ているままをやって「できたできた♪」と喜ぶのも芸がない ので、いろいろいぢってみる。

こんなの

  1: #! c:/loka/ruby/bin/ruby
  2: 
  3: require 'tk'
  4: require 'tanshi'
  5: 
  6: tanka = Tanka.new
  7: 
  8: # ファイル名をつくるために
  9: $default_fname = Time::new.strftime("waka%y%m%d.txt")
 10: print $default_fname, "\n"
 11: 
 12: # 直前に書込されたファイル名
 13: $jxus_skribita = $default_fname
 14: 
 15: # タイトルバーをそれらしく
 16: $root = TkRoot.new				# (A)
 17: $root.title('へらさけの はなもげらわか あのよろし')
 18: 
 19: font = TkFont.new(['mincho', 12])
 20: 
 21: # メニューバーとメニュー
 22: TkFrame.new{ |f|				# (B)
 23:   pack('fill' => 'x', 'side' => 'top')
 24:   # Fileメニュー
 25:   TkMenubutton.new(f, 'text' => 'File'){|mb|	# (C)
 26:     pack('side' => 'left')
 27:     mb.menu(menu = TkMenu.new(mb, 'tearoff' => false))
 28:     menu.add('command', 'label' => 'New')	# (D)
 29:     menu.add('command', 'label' => 'Open...',
 30: 	     'command' => proc{do_file_open})
 31:     menu.add('separator')
 32:     menu.add('command', 'label' => 'Save...',
 33: 	     'command' => proc{do_file_save})
 34:     menu.add('separator')
 35:     menu.add('command', 'label' => 'Quit',
 36: 	     'command' => proc{do_quit})
 37:   }
 38: 
 39:   # Editメニュー。マッキントッシュのご作法を忘れてしまた。
 40:   # 選択範囲とクリップボードの合わせ技になるので今は手が回らない。
 41:   # 後の楽しみにとっておこう。
 42:   TkMenubutton.new(f, 'text' => 'Edit'){|mb|
 43:     pack('side' => 'left')
 44:     mb.menu(menu = TkMenu.new(mb, 'tearoff' => false))
 45:     menu.add('command', 'label' => 'Undo')	# (E)
 46:     menu.add('separator')
 47:     menu.add('command', 'label' => 'Cut')
 48:     menu.add('command', 'label' => 'Copy')
 49:     menu.add('command', 'label' => 'Paste')
 50:     menu.add('separator')
 51:     menu.add('command', 'label' => 'Clear All', 
 52: 	     'command' => proc{do_clear_all})
 53:     menu.add('separator')
 54:     menu.add('command', 'label' => 'Select All')
 55:   }
 56: }
 57: 
 58: # (F)
 59: def do_file_open
 60:   Tk.getOpenFile
 61: end
 62: 
 63: def do_file_save
 64:   fname = Tk.getSaveFile('initialfile' => $jxus_skribita)
 65:   if fname == '' then return end
 66:   # ここで参照するテキストウィジェットは広域変数でなければならな
 67:   # い。メニューコマンドの定義をテキストウィジェット生成の後にす
 68:   # れば局所変数でも大丈夫なのだろうが。
 69:   begin
 70:     open(fname, "w") do |f|
 71:       f.write($txt.get('1.0', 'end'))
 72:     end
 73:   ensure
 74:     if(fname != $jxus_skribita) then
 75:       $jxus_skribita = fname
 76:     end
 77:   end
 78: end
 79: 
 80: def do_quit		# (G)
 81:   # ダイアログを作りたい。TkFrameの応用でできないかな?
 82:   v = TkVariable.new('momo')
 83:   dlg = TkToplevel.new(nil){ |w|
 84:     TkLabel.new(w, 'text' => 'Cxu vi vere deiras?').pack
 85:     TkButton.new(w, 'text' => 'Jes'){
 86:       # 本当はボタンが押されてこのブロックを抜け出た後でdestroy
 87:       # したいのだが、うまく行かないのでここで呼んでしまう。
 88:       # ちょと乱暴。
 89:       command(proc{
 90: 		  $root.destroy
 91: 	      })
 92:       pack('side' => 'left')
 93:     }
 94:     TkButton.new(w, 'text' => 'Ne'){
 95:       command(proc{
 96: 		v.value = "no"
 97: 		w.destroy
 98: 	      })
 99:       pack('side' => 'right')
100:     }
101:   }
102:   # 上のコードだけでは「ダイアログの中に制御が留まる」は実現でき
103:   # ず、下のprint文が実行されてしまう。何らかの仕組みが必要。
104:   print "#{v.value}\n"
105: end
106: 
107: def do_undo
108: end
109: 
110: def do_cut
111: end
112: 
113: def do_copy
114: end
115: 
116: def do_paste
117: end
118: 
119: def do_clear_all			# (H)
120:   $txt.delete('1.0', 'end')
121: end
122: 
123: def do_select_all
124: end
125: 
126: # 短歌表示領域を乗せるフレーム
127: frm1 = TkFrame.new.pack('side' => 'top', 'anchor' => 'n')
128: 
129: # 短歌表示領域は上に。スクロールバーをつける	(I)
130: sb = TkScrollbar.new(frm1).pack('fill' => 'y', 'side' => 'right')
131: 
132: $txt = TkText.new(frm1, 'font' => font,
133: 		 'width' => 40,
134: 		 'height' => 20
135: 		 ).pack('side' => 'right')
136: 
137: # これらはそれぞれのnewメソッドに渡すブロック内で書いても怒られ
138: # ないが、実行時エラーになる。sbなりtxtなりはそれぞれのブロック
139: # 内スコープで束縛されるので未定義シンボルになるためと思われる
140: sb.command(proc{|args| txt.yview *args})
141: $txt.yscrollcommand(proc{|first, last| sb.set(first, last)})
142: 
143: # フレームを使ってボタンを左右に配置		(J)
144: frm2 = TkFrame.new.pack('side' => 'bottom', 'anchor' => 's')
145: 
146: TkButton.new(frm2, 'text' => '詠む'){
147:   command(
148: 	  proc{
149: 	    isshu = tanka.generi
150: 	    isshu << "\n\n"
151: 	    # ここではtxtはローカル変数でも参照できた。トップレ
152: 	    # ベルのローカルスコープに入っているからか
153: 	    $txt.insert('end', isshu)
154: 	    $txt.see('end')
155: 	    print "--> #{isshu}"	# デバッグ文
156: 	  })
157:   pack('side' => 'left')
158: }
159: 
160: Tk.mainloop
161: 
162: # end of file

解説

  1. TkRootは「ルートウィジェット」で、すべての部品が乗るおおもと のウィンドウ。これは作らなくても問題ないが、作っておくとこの 人のメソッドを呼び出していろいろできる。
  2. メニューバーを乗せるためにフレームを用意する。言い換えれば、 フレームの上にメニューを配置する。これがTcl/Tkでのやり方とい うことのようだ。
  3. ひとつのメニューボタンがひとつのメニューを表す。実際にはこれ にさらにメニューTkMenuを乗せる。
  4. メニューにメニューアイテムを追加する。
  5. ソース中のコメントにもあるように、この部分は未実装。
  6. 以下はメニューハンドラ(メニュー項目が選択された時に呼ばれる 手続き)。殆ど何もしてません。
    Tk#getOpenFileとかTk#getSaveFileとかはtcl/tkにある同名の手続 きのRuby版で、MS-WindowsではWindows標準のあのダイアログボッ クスが出てくる。
  7. Ruby/Tkにはダイアログボックスというのが実装されていないよう に見える。せっかくだから挑戦してみた。ただしソース中で自白し ているように、未完成。
    ここで欲しいのはモーダルダイアログなのに、まだモーダルにでき ていない(一般的にはモードレスの方がプログラムが難しくなるも のだが、Tcl/Tkではモードレスの方が楽に書けるらしい)。本当は 変数v (TkVariable)を介してどのボタンを押したかをダイアログボッ クスから呼出し側に伝えてもらい、それに従って終了するかしない かを決める、という風にしたい。
    頑張って書けば、モジュールにまとめられるくらいのものができる かも(いやいや、こういうのはもうとっくに誰かが手をつけている)。
  8. 全部を削除するのは簡単にできたのでこれだけは実装した。
  9. テキストウィジェットとスクロールバーの連動は256倍本からい ただいた。
  10. ボタンがひとつに減ったのでフレームを使う必要はなくなったんだ けど。

 ついこの間Rubyに触り始めたのに加え、Tcl/Tkも詳しく知らない状態 では、この辺りが限度。

 しかし見方を変えると、Tcl/Tkのまともなリファレンスが手許にあれ ば、後は見よう見まねと試行錯誤でなんとかやっていけるのではないか と思える。「挑戦してみよう」という気持になりやすいのはうれしい。

補足

 Ruby/Tkといっても、流儀のようなものがいくつかあるそうだ。ひと つはここに紹介したように「TkのウィジェットをRuby風に操る」方法で、 ご覧のようにメソッド名やウィジェットの属性がTkであることを物語っ ている以外はすっかりRubyである。tk.rbというライブラリを使う。も うひとつは、Tck/Tkのコマンドをほぼそのまま書いて呼び出す流儀で、 tcltklib.rbというライブラリを使う。

 ぼくは初めは後者の方法、つまり「Tcl/Tkの文を書いて、Rubyから呼 び出す」というので始めようと思っていた。どうせTcl/Tkのウィジェッ トを使うのならTcl/Tkが見えてもいいだろうと思ったし、隠されること によって自分で細かいことをやりたくなった時に途方にくれるのは厭だ し、コードが短くなりそうだし、動作もこの方が速い(感じたのは立ち 上がり時。実行速度は判らない)。

 しかし、そのやり方ではTcl/TkのコードとRuby のコードがどのよう に連携をとるのか判らない。GUI を操るのはTcl/Tk部分だが、実際に何 か仕事をするのはRubyのコードだ(そうでなければわざわざRubyから呼 ぶ必要もなく、ぜんぶTcl/Tkで書けばよい)。この両者の間の情報やデー タの受け渡しの仕方が判らないので諦めた。何しろ文献がものすごく少 ないのだ。

 いくらか書いてみると、立ち上がりが遅いのも慣れれば平気だし(笑)、 Rubyらしく書けるのはやはり重要だ。

ところでI18Nはどーするんだろう

 スクリプトレベルでGUIプログラミングを楽しめるようになったのは いいけれど、I18N(InternationalizatioN)とかL10N(LocalizationN)は どーなるんだろう。Xウィンドウシステムのメッセージカタログ方式は 使えなさそうに見える。自分が書いたスクリプトが、あまりに出来がよ くてよその国で使われ始めた、ということはありそうな話だ。その時に 言語の違いをどう処理するのか。

 もしかしたら国際化・地域化は「スクリプト言語」「インタープリタ」 の意外な弱点かも知れない。

 まースクリプトだから気にしなくてもいいのかも(笑)。Rubyプログラ ム内で環境変数LANGを見て分岐するとかできなくはないし。でも誰か既 にそういうところまで面倒見るモジュールなど書いているのでは。

寄り道

 Tcl/Tkの最大の功績は何といってもGUIを手軽に気楽にプログラムで きるようにしたことに違いない。開発されたのは1990年代の初め頃だっ ただろうか。身の回りで話題に上るようになったのは1994,5年頃だった ような気がする。その頃、Tclとはまったく何の関係もなく、「かんたん グラフィックライブラリ」といったものを考えたりしていた。GUIを記述 できるスクリプト言語レベルのものを考えもした(まさにTcl/Tk)。

 おそらく他にも同じようなことを考えた人はたくさんいる筈だ。 GUIを構成する部品はウィンドウシステムが提供してくれるし、動的に 生成して表示することもできる。多くは定型的なものだ。これらの使い 方を記述する規則を言語に仕立てられれば、定型的で面倒な仕事をイン タープリターが請け負って、GUIプログラミングは楽になる。遥か太古 の昔(というほどではないけど)にはマッキントッシュのHyperCardと いうのもあったから大した思いつきではない。で、ぼくなどが作るまで もなく、実際に楽になったというわけだ。

 個人で使うツールなどにはこれで充分だと思うし、仕事でも事情が許 せば使っていいのではないか。もはやCでごりごりGUIする時代じゃあり ませんね(いや、そういうこともまだ必要になることもあり得るし、も のの仕組みを知る上では一度は勉強しておいた方がいいには違いないが)。

 しばらく前に、Solarisで必要があって超簡単なGUIプログラムを作る 機会があった。アプリケーションビルダというものの助けを借りて、仕 上がるまでに1〜2人日くらいかかったのだけれど、Tcl/Tkとかで書けば 数時間だったのだろう。しかしそれはお堅い仕事で、サーバーに不必要 なプログラム(言語処理系も含む)は載せてはいけないのだった。 Ruby/Tkともなればふたつの処理系をインストールしておく必要がある。 障害発生時にスタンドアロンのプログラムならそれだけを調べれば済む のに対して、調査が面倒になるということでもある。

 ということはあるにしても、楽をしたかったり楽しみたかったりする 分にはいうことのない環境ができてくれたことは素晴らしい。

さらに寄り道

 よんどころない事情からMS-Windows Meを搭載するLibretto L1という PCを半年ばかり使っていた。Librettoには不満はなかったのだが、オペ レーティングシステムには大いに文句があった。もしそれをオペレーティ ングシステムと呼ぶならばだが。

 「いつかFreeBSDに入れ替えてやる」といつものことを思いながらだ ましだまし使っていたのだけれど、Rubyの勉強を始めるようになって、 我慢ならなくなった。RubyはUnix生まれの言語であり、仕様にUnixを想 定したところが多多あるほか、処理系もCUIを基本としている。それで ぼくもtcshから動かしたりするのだが、

tcshは「ティー・シーシェル」と読む。BSD系Unixの標準シェルである csh(シーシェル)を拡張・改良したもの。MS-Windowsに移植されても いる。
tcshはTWENEX Cshellの略だそうだ。伝説のOS・TWENEXのシェルも相当 使いやすかったと見える。

MS-Windows Me のコンソールアプリケーション回り(何かのドライバな のかエミュレータなのかプロセス管理なのかどこなのかは皆目見当もつ かない)は かなり不安定で、危なっかしくて使っていられな い。

 まずまともに使い続けることができない。ちょっとしたことでコンソー ルウィンドウがうんともすんとも言わなくなるし、何ともないようでも 数回Rubyを動かすとだんまりを決め込んでしまう。それだけならばまだ よいが(よくないんだよ)、ハングアップしたそのプロセスを殺すと MS-Windows Me というOS自体(もしそれがOSであるならばだが)が具合 悪くなってしまうようなんだな。MS-Windows MeがWin95以来の流れを継 ぐ、16ビットコード・32ビットコード混在のシステムであるだろうこと は一見して判るが、なぜこんなものを出荷したのか理解に苦しむ。 MS-Windows 2000はなかなかにこなれて、「素人さん」でもそこそこ使 えそうな仕上がりになっているのだが。

 こんな調子ではストレスばか り溜まって Rubyの勉強などできない。それどころか けっきょく自分がやりたいこと がすべてできない。何かほんの二年前にも同じことを経 験した気がする。しかし以前ほどゆとりがないのでBSDへの復帰は見送っ て、ひとまずMS-Windows 2000に避難することにした。

 おかげで少しは快適である。よーしばりばりRubyで遊ぶぞ。しかし、 誰の「おかげ」なんだ一体? 何を喜ぶことがあるんだ? (--#)

(2002.03.31)

Rubyプログラミング狂室へ
参考図書へ
コンピューター言語研究所へ
トップページへ
(C) ©Copyright Noboru HIWAMATA (nulpleno). All rights reserved.