徹夜していたら気が向いたので BitChannel の to do を簡単なやつからいくつか潰した。 BitChannelComment に書いてあったやつと、 rev 1.1.1.1 の件、それから CSS の @media=print、 あと大物としては revision kill か。 ま、詳しいことは BitChannelNews でも見てください。
バグレポートってどこにしてもらうのがいいのかねえ。 いまはテストのつもりで Wiki に置いてるけど、ML のほうが便利だろうか。 それとも Wiki のままで diff RSS を実装したほうが流行に乗れるかな。
(15:23)
なんか次から次へとバグレポートが来るんですけど。 こんなんじゃコミットどころじゃないっつーの。
そういえば現在の Ripper は ruby 1.9 専用っすよ。 ruby 1.8 で使うと落ちます。 両バージョン対応はめんどくさいからやりません。
(16:18)
世間の波に置いていかれまくり。
るびま、というのは Rubyist Magazine ですか。 けっきょく無難なやつになったんだ。
ちなみに「るびま」って「ネギま!」みたいだなとか思うわたしはダメですか。 そうですか。
そーいや日本 Ruby の会に登録しようかなあ。 今年はたいしたことはできそうにないけど……。
(16:24)
NetBSD の df に -h オプションがなかったので Ruby でラッパーを書こうかと思ったんだけど、 うっかり
out = `df -k` ...
とか書いて、それを ~/bin/df にインストールしてしまった。 で、df。
当然無限に自分を呼び続けることになってすごい勢いでリソースを消費していく。 ps してる間にもプロセスが増えていくので kill じゃ対処できないし、 killall ないし (あってもだめだったかも)、 もしかしてログアウトしたらねこそぎにできるかと思って exit してみたんだけど、ハードディスクの音が止まる気配もない。 ついでにメモリ不足で ssh もつながんないし、 シリアルコンソールはセットアップするの忘れてるし、 コンソールはもともとついてないし、で詰み。 しかたがないので電源切って落とした。 本当はどうするのがよかったんだろう。
(18:33)
DEC Celebris GL-2 2622 を買いました。 Pentium II 333MHz を二個のせてパワーアップだ!
最近外向けサーバの負荷が上がってきてるので、 せめて dual にしてやろうかと。
(21:06)
何の意味があるのかよくわからないが、 Ruby の fork ブロックから break してみた。
% test-all-ruby -e 'fork { break }; Process.wait' ruby-1.4.6: /tmp/rbG3fnQt:1:in `wait': No child processes (Errno::ECHILD) from /tmp/rbG3fnQt:1 ruby-1.6.0: -e:1:in `wait': No child processes (Errno::ECHILD) from -e:1 ruby-1.6.1: -e:1:in `wait': No child processes (Errno::ECHILD) from -e:1 ruby-1.6.2: -e:1:in `fork': unexpected break ruby-1.6.3: -e:1:in `fork': unexpected break ruby-1.6.4: -e:1:in `fork': unexpected break ruby-1.6.5: -e:1:in `fork': unexpected break ruby-1.6.6: -e:1:in `fork': unexpected break ruby-1.6.7: -e:1:in `fork': unexpected break ruby-1.6.8: -e:1:in `fork': unexpected break ruby-1.8.0: -e:1:in `fork': unexpected break ruby-1.8.1: -e:1:in `fork': unexpected break ruby: -e:1:in `fork': unexpected break
まあ、そんなもんかねえ。
じゃあ next はどうよ?
% test-all-ruby -e 'fork { next }; Process.wait' ruby-1.4.6: ruby-1.6.0: ruby-1.6.1: ruby-1.6.2: ruby-1.6.3: ruby-1.6.4: ruby-1.6.5: ruby-1.6.6: ruby-1.6.7: ruby-1.6.8: ruby-1.8.0: ruby-1.8.1: ruby:
そりゃそうか。じゃあ redo ……
% test-all-ruby -e 'fork { redo }; Process.wait'
って、無限ループじゃん。
(23:16)
うーむ、現場ではあせって対処不可能になった無限 fork ですが、 やっぱりいろいろと回避方法があるもんですねえ。 勉強になりました。 まとめると、まず予防策としては
すでに発生している場合は
てなとこですか。 man に載ってる方法もあるんだから、 落ち着けばなんとかなったかもしれないな。 まずは冷静になるのが大切か。
(02:10)
Referer SPAM 対策を考えるついでに disp_referrer のキャッシュ方法を考えた。 tDiary ではリファラは常に最新の日付にだけ蓄積されるので、 最新日より一日以上古いリファラは決して更新されない (と思う)。 つまり update.rb が日付をまたいだ時点で 前日分のリファラを処理して結果だけ保存すれば十分なはずだ。 (ちなみに最新日だけは覚悟を決めて毎回計算する。)
で、それと微妙に関連するんだけども、 tDiary のデータベースは日単位にしたらどうなんだろう。 例えばこんな感じのディレクトリ構造にする。
200401/ 01/ property …… Visible とか Format とか data …… 日記 comment/ …… ツッコミ 01/ property data 02 : referer …… リファラ 02/ 03/ :
qmail のごとく、property をさらに細かくファイルに分けてもよい。 今の実装だと日付関係の計算をするためには一月まるごと読まないとならないけど、 ファイルが日単位になってればツリーのトラバースだけで済むのが利点だ。
……なんか実装したくなってきた (しないけど)
(18:07)
いくらなんでも重なりすぎ! (笑)
(19:27)
[ruby-list:40021] から始まる「クラス名→クラスオブジェクト」の話だけど、 ちょっと細かいことになるのでこっちに追記。
クラス名からクラスオブジェクトを得る方法として
eval(classname) classname.split(/::/).inject(Object) {|c,name| c.const_get(name) }
の二つがある。 で、このうち eval は危険だとすぐわかるのでみんな注意する。
しかし、この場合に eval が危険なのは classname をリモートからかなり自由に指定できる場合である。 classname が定数なら「eval しているから危険」とは言えない。
そしてまた、引数が自由になるのであれば、 同じ状況で const_get を使ったところで安全とは言えない。 例えば classname に任意文字列を指定できるとすると、 (任意のクラス).new (など) が呼べることになる。 すぐには具体的な例が思いつかないんだが、 DL とか Win32API とか IO あたりを new すると それなりに危ないことができそうな予感がしないでもない。
ちなみに $SAFE=1 でも Object.const_get("IO".taint) は通るので $SAFE も当てにならない (シンボルは汚染されない)。
(23:00)
(追記) やっぱり new だけですごくやばいことができる例がないとイマイチ説得力がないな。 任意のコードの eval に比べると攻撃力が違いすぎ。
頭からその存在をすっかり忘れていた tdiarysearch だが、 バグ報告が来てるな。
うーむ……。 category.rb が独自にやってる load_plugin が tdiarysearch のとバッティングしてるのかな。 とりあえず再現させるのが先決か。
(13:36)
次の本を書いています。 またまた Ruby を裏切って Linux プログラミングの初心者本です。 まあでも、中で Ruby の話もしてるから裏切ってはいないか……。
できれば夏休み中に完成させたいんですが、どうなることやら。
(13:38)
http://jp.rubyist.net/magazine/?0001
Rubyist Magazine (略して「るびま」) 第 1 号が出ましたね。 関係者の皆さまお疲れさまでした。
いやー
いやー
いやー……
巻頭言が面白すぎるんですけど! 「〜の発刊に際して」の時点でコーヒー吹いたよ!
Nora チュートリアルはフツーに参考になった。 ありがちなインストール記事がないけど、いいんだろうか。まあいいよな。 (そういえば、インストーラまわりをちゃんと追うと一本書けそうだ)
Ruby de GUI、今回は第一回だからしょうがないのだけど、 並列に並べるよりは思いきり偏った内容のほうが参考になりそうです。 そのへんは次回以降に期待してます。
あと、NaCl レポートできっちりサーバ室を撮ってきた笹田さん GJ! まつもとさんインタビューも今まではあんまりなかった感じだな、 というか日記の投票ネタを全部つっこんだね?
でもって「紅い隕石」さん…… 文章を読むと中の人が一瞬でわかるんですが (笑)
明後日 12 日に Ripper をコミットします。今度ばかりは本気です。 今日と明日でないのは明日が本の第一締切だから。 変なバグが報告されてたのでコミットを延期してたんですが、 問題が起きているのは Ruby 1.8.x だったということがわかったので とりあえず懸案はなくなりました。
なお、当然ですがコミット対象は HEAD のみです。 安定版に入れられるような状態ではありません。
(15:32)
Ripper を ruby 本体 (parse.y, ext/ripper) にコミットすべく作業中です。 12 日に作業を始めるのも、まあ 12 日のうちだよな。
何かまずそうならツッコミ入れてください。
(22:45)
procfs のエントリを変更しようとするとどうなるか実験してみた。
/proc/1526 % uname -srm Linux 2.4.22 i686 /proc/1526 % ruby -e 'File.rename "fd", "_fd"' -e:1:in `rename': No such file or directory - fd or _fd (Errno::ENOENT) from -e:1 /proc/1526 % ruby -e 'File.unlink "maps"' -e:1:in `unlink': Permission denied - maps (Errno::EACCES) from -e:1 /proc/1526 % sudo ruby -e 'File.unlink "maps"' -e:1:in `unlink': Permission denied - maps (Errno::EACCES) from -e:1
ということで、rename すると ENOENT、 unlink すると root であろうと EACCES になるようだ。 へー。
(22:46)
とりあえず parse.y から入れてみる。
うぉぉ test/ruby/test_eval.rb で SEGV しやがった!
と思ったが ripper に関係なく元から SEGV していた。よかったよかった (マテ)
(00:33)
(追記) [ruby-dev:24228] のやつだな。
lib/ripper.rb をコンパイル時に作るにはどうすればいいんだー
あー、どうしよう。 プラットフォームごとに違うものじゃないし、 開発者 (つまり俺) が作って ci することにしようかなあ。 ripper.rb が変わるのってイベントの互換性がなくなるときだし、 あんまり頻繁に変えていいものではないのは確かなんだよな。
(01:32)
見張ってるページの更新された部分だけを表示してくれる アンテナってのは結構あるけど、その更新された部分に 各サイトの CSS を適用して表示するってのはできないかなあ。
(10:14)
……寝すぎた。
syslog に
Sep 14 17:14:43 harmony kernel: EXT2-fs error (device ide0(3,65)): ext2_read_inode: unable to read inode block - inode=6848513, block=13697032
なんてのが出てるのがとても気になるが、 ハードウェア損傷でなければよいことにする。
ああ……。 はい入れましたでは済まないだろうとは思ってたけど まさかここまでいろいろ問題が出てくるとはなあ、 というか Borland make をいじめてやりたい。キュッと。
しかしどっちかというと気になるのは [ruby-core:03391] かなあ。
(18:18)
えーとまずはわたなべさんの Borland make 向けパッチをとりこむか。 ルールを減らしてパイプを削除して……でいいかな。 俺専用ルールは Makefile.dev に分離しておこう。
http://www.dm4lab.to/~usa/ruby/d/200409b.html#id20040914_P1
うささんのコード例を見ていて、 method_missing を追放しようと決意する。 スキャナ・パーサに関らずすべてのイベント発生を フックできる on__ANY を作ればいいだろうか。
……と思ったけど、 上記のコードなら on__scan のがよさそうだな。 全イベントフックは他に役立つ例を見付けてからにしよう。
ちなみにヒアドキュメントまわりは Ripper でも一番嫌なことを している部分なのでトークンをロストするくらいは日常茶飯事なのだ! (いばるな)
(19:39)
[ruby-core:03391] は BSD make で発生する問題だったようだ。 NetBSD/Alpha でやったら再現した。 Borland make 用の変更で偶然 BSD make も通るようになった模様。
(22:09)
http://takahr.dhis.portside.net/cgi-bin/rwiki.cgi?cmd=view;name=%C6%FC%BB%EF
setup.rb のドキュメント? …… http://i.loveruby.net/ja/man/setup/ だけでは不足ってことでしょうか。
でも extconf.rb も入っているということは、 本当に知りたいのは「extconf.rb や setup.rb」じゃなくて 「Ruby で書いたアプリケーションやライブラリのパッケージの作りかた」 (Ruby 開発者向け文書?) なのかなあ。 もうちょっと意図が明確にならないとなんとも言えません。
(02:23)
あは
HDD クラ――――――――――ッシュ!!
(14:58)
いや違った不良セクタだけか。 まあ、どっちにしても棺桶に片足つっこんでるか首までつかってるかの違いだよなあ。 片足だけのほうが復旧は楽だけどさ。
超・運のいいことに昔使っていた /home 領域を空けたままにしてあったので、 とりあえずそっちに引越すことにする。
それにしてもえらい手狭になってしまった。 ~/src をほとんど捨てても空きが 2.6GB (使用率 74%) しかない。 しょうがないので明日にでも HDD を買ってこよう。
(20:55)
HDD を買いに池袋まで行ったのに、 銀行のカードを忘れて買えんかった……。
ちょっと今日明日は余裕がないので ripper 関係は 日曜以降に落ち着いて対応します。すんません。
(21:11)
ドキュメントとサンプルも同様……というか、 このへんはせめてもうちょっと仕様が安定してからにしたいのね。 現在は仕様も実装もあまりに流動的なんで。
(22:12 追記)
http://www.quiter.jp/news/saga/040915002261.html
(つかれたより)
http://www.square-enix.co.jp/games/ps2/romasaga/
まじか――――――――――――――ッ!
え、うそだろ、本当にロマサガ 1 リメイク? マジで? うわーもうこれは買うよ PS2 ごと買うよ どんなに改悪されても買ってしまうよ! 聖剣伝説のリメイクはひどかったらしいが、 今度ばかりはほんとに頼んます。
あ、そうだ、ルピーシステムは遠慮なく消してください。
(23:46)
コメント SPAM が収まったようなのでツッコミ機能を復活させた。 しかしすげえよ。機械的に全日付に POST してきてたもんな。
対策はどうしようかな。 あらかじめ "delete_me" っていう文字列を入れといたテキストボックスを置いて、 それを消さない限り登録しないというのはどうだろう。 いやチェックボックスのほうが簡単か。
→ かんたんに実装してみた。
(11:54)
とりあえず ripper のパッチだけとりこんじゃうかな。
rb_reserved_word は static だとずっと思い込んでた。 よく見たら extern なんだな。
よくよく考えると、 本体と Ripper のトークンはバイナリ的に同じじゃなきゃだめなんだから、 別々の yacc で処理するだけでも危険なんだ。 そういう意味では本体も bison 必須になったのは都合がいい。
lex_strterm の件は迷う。 せっかく本体が再入可能になったことだし、 yyparse の再帰で処理できないもんだろうか。
(06:11)
parser->buf の役割をやっと思い出した。 Ruby レベルではトークンが tokbuf に保存されるから 入力バッファを捨てられるけど、 Ripper では行をまたぐトークンがあるからまずいんだ。
あと、ヒアドキュメントのまわりで current_position を退避・復帰してないのもまずい。 これもやはり入力バッファから書き直したほうがよさそう。
やっぱり入力バッファは Ripper 専用に書き直すしかないな。
(06:37)
(14:28 追記) やっぱやめた。
がんばって current_position を退避・復帰するか Ripper#pos を捨てるかの選択を迫られた結果、 あっさり Ripper#pos を捨てることに大決定。 ま、lineno と column があるんだからなんとかなるでしょ。
(13:12)
次はこれか……。
~/c/ruby % cat -n t 1 print(<<EOS) 2 1 3 2a#{}2b 4 3 5 EOS ~/c/ruby % ruby -rpp -rtokenizer -e 'pp Ripper.tokenize(ARGF.read)' t [[1, 0, :on__ident, "print"], [1, 5, :on__lparen, "("], [1, 6, :on__heredoc_beg, "<<EOS"], [3, 0, :on__tstring_content, "1\n"], ← 行番号ズレ [3, 0, :on__tstring_content, "2a"], [3, 2, :on__embexpr_beg, "#{"], [3, 4, :on__rbrace, "}"], [5, 0, :on__heredoc_content, "2b\n"], ← 行番号ズレ [5, 0, :on__heredoc_content, "3\n"], ← 行番号ズレ [5, 0, :on__heredoc_end, "EOS\n"], [1, 11, :on__rparen, ")"], [1, 12, :on__nl, "\n"]]
ヒアドキュメント本体のイベントが場合によって違う。 行番号もおかしい。文字列が複数行になっているときも同様。 さてこれを直すのはどうすれば……
意外に簡単だった。
~/c/ruby % cat -n t 1 print(<<EOS) 2 1 3 2a#{}2b 4 3 5 EOS ~/c/ruby % ruby -rpp -rtokenizer -e 'pp Ripper.tokenize(ARGF.read)' t [[1, 0, :on__ident, "print"], [1, 5, :on__lparen, "("], [1, 6, :on__heredoc_beg, "<<EOS"], [2, 0, :on__tstring_content, "1\n"], [3, 0, :on__tstring_content, "2a"], [3, 2, :on__embexpr_beg, "#{"], [3, 4, :on__rbrace, "}"], [3, 5, :on__tstring_content, "2b\n"], [4, 0, :on__tstring_content, "3\n"], [5, 0, :on__heredoc_end, "EOS\n"], [1, 11, :on__rparen, ")"], [1, 12, :on__nl, "\n"]]
(heredoc_content イベントを送るのはあきらめた。)
理想的には入力での並び順にイベントを発生させたいところだが、 ネストした場合を考えるとちょっと難しいな。 どのタイミングでフラッシュすればいいんだか。
これも思ったより簡単だった。
~/c/ruby % cat -n t 1 print( <<A , <<B) 2 1 3 2a#{}2b 4 3 5 A 6 1 7 2 8 B 9 %w(a 10 b ) ~/c/ruby % ruby -rpp -rtokenizer -e 'pp Ripper.tokenize(ARGF.read)' t [[1, 0, :on__ident, "print"], [1, 5, :on__lparen, "("], [1, 6, :on__sp, " "], [1, 7, :on__heredoc_beg, "<<A"], [1, 10, :on__sp, " "], [1, 11, :on__comma, ","], [1, 12, :on__sp, " "], [1, 13, :on__heredoc_beg, "<<B"], [1, 16, :on__rparen, ")"], [1, 17, :on__nl, "\n"], [2, 0, :on__tstring_content, "1\n"], [3, 0, :on__tstring_content, "2a"], [3, 2, :on__embexpr_beg, "#{"], [3, 4, :on__rbrace, "}"], [3, 5, :on__tstring_content, "2b\n"], [4, 0, :on__tstring_content, "3\n"], [5, 0, :on__heredoc_end, "A\n"], [6, 0, :on__tstring_content, "1\n"], [7, 0, :on__tstring_content, "2\n"], [8, 0, :on__heredoc_end, "B\n"], [9, 0, :on__qwords_beg, "%w("], [9, 3, :on__tstring_content, "a"], [9, 4, :on__words_sep, "\n"], [10, 0, :on__tstring_content, "b"], [10, 1, :on__words_sep, " )"], [10, 3, :on__nl, "\n"]]
(14:26)
しまったあああああああああ! スキャナで dispatch した値をパーサに伝えてない! うっわー、これは致命的。 値を渡すことを考えるとトークンを並べなおすなんて不可能だ。 パーサイベントも遅延させるか……だめだ……無理すぎる……。
いや待てよ、遅延スキャナイベントですらまずいな。 一回パーサに制御を戻さないことには yylval を拾ってくれない。 つまり無理矢理でも一つのトークンにまとめないとだめだ。
うわー……最悪だ……。 まさかここまでバカやってるとは思わなかった。
(15:47)
こんなものを作ってみたのだが……
% ruby -rripper/sexp -rpp -e 'pp Ripper.sexp("def m(a,b=1,*c,&block) end")' [:program, [:stmts_add, [:stmts_new], [:def, [:@ident, "m", 1, 4], [:paren, [:params, [[:@ident, "a", 1, 6]], [[[:@ident, "b", 1, 8], [:@int, "1", 1, 10]]], [:restparam, [:@ident, "c", 1, 13]], [:@ident, "block", 1, 16]]], [:bodystmt, [:stmts_add, [:stmts_new], [:void_stmt]], nil, nil, nil]]]]
これって S-Expression なんですかね。
(17:11)
lib 以下のライブラリを片端からパースして再現できるかどうか試してみた。
まあ当然と言うかやっぱりと言うか、diff が出るね。
後者は気付いてたし、直すのも簡単。
前者は何だろうなあ。また yylex をぐるぐるまわらないといけないのか……。 でもさすがに疲れたし、原稿書きに戻りたいんで、続きはまたこんど。
しかし、これ以外に何も出ないというのは結構収穫だ。 SEGV もしなかったしな (といっても SEGV しやすいのはパースイベントで、 今回のテストはパースイベントを使っていないのであまり当てにはならない)。
(18:41)
そーいや Ripper の使いかたって書いたことなかったんでちょっとだけ書いとく
基本
# けーしょーします class MyParser < Ripper # イベントハンドラをていぎしてみます def on__comment(tok) print tok end end # きどうします MyParser.new(ARGF).parse
いじょ
一歩進んでみる
(19:23)
■
oxy [はじめまして。AST作者の吉田(HN:oxy)と申します。
このプログラムは自分の目的を果たすためにとりあえず書いたもので、場当たり的な所が一杯あるのは自覚しているのですが、それでも公開すれば誰かの助けになるかもしれないと公開したものです。
ですから、もしご不満があれば直しますので、今回の日記の最後の行の真意を教えていただけないでしょうか。]
■
青木 [え? いや、たぶんそれは違うライブラリじゃないかと思います。
ぼくが言っているのは RubyForge のレポジトリに入っているやつでした。
ちなみに RubyForge のほうに関して思ったのは、「名前を合わせて
ほしかった」というただそれだけです。
わざわざほのめかすような書きかたをしてすみませんでした。]
■
青木 [↑「RubyForgeにある、Ripperのレポジトリに入っているやつ」
(lib/ripper/ast.rb) のことです。メンテしているのは
mark sparshatt で、オリジナルは別にあったはず。]
http://www.tokyo-nazo.net/%7etester/kako/best.html#20031015
10 月は HDD が一番壊れやすい月なんだってさ。へー。
(08:15)
Ripper::Tokenizer はやめて Ripper::Filter というクラスを作った。
特徴
……まあようするに、 うささんとこの色付けスクリプトみたいのを作るために特化したクラスです。
require 'ripper/filter' class ColorizeFilter < Ripper::Filter def on__default(event, tok, out) out << tok end def on__tstring_content(tok, out) out << %Q[<span class="string">#{tok}</span>] end def on__comment(tok, out) out << %Q[<span class="comment">#{tok}</span>] end # 以下略 end result = ColorizeFilter.new(ARGF).parse('')
と、こんなふうに書ける! これはいいなあ。
こう考えてみると、実は on__scan っていらないんじゃないだろうか。
というか、on__ じゃなくて on_ でいいんじゃないだろうか (orz じゃないよ)。
(08:53)
だんだん考えがまとまってきた。 Ruby プログラムをテキストとして扱いたい人たちには Ripper::Filter があればほぼ十分だろう。 あと必要なのは一発で構文木を作るインターフェイスと、 構文木自体を自分で作りたい人向けのインターフェイスだな。 で、前者は誰ぞが作ってくれたやつがあるので、それを持ってくればよい。 後者は Ripper クラスを直に使ってもらえばいいだろう。
もともと on__scan は Ripper::Filter みたいな用途のために付けたわけだが、 定数 SCANNER_EVENTS を付けたことで必要なくなったと考えていい。 on__scan は semantic value の扱いに関してもうまくないので邪魔だ。 やはり on__scan は捨てよう。
on__ のほうはちょっと迷うけど……。 うーん、どうしようかな。on_ でいいかなあ。 誰か決めてー!
(09:09)
ちなみになんで on_ じゃなくて on__ かと言うと、 「Ripperは継承しないといけないし on_ だと他のモジュールのメソッドと重なりそうだよなー」 という程度の理由しかなかったりする。
(09:13)
どさくさに紛れて setup.rb をアップデートしてみた。 評判の悪い config setup install をついにあきらめて
$ ruby setup.rb
で全部やるようにした。 もちろん元の config setup install でもできるので 細かく制御したければそっちを使ってください。
ついでに
$ ruby setup.rb all --prefix=$HOME
なんてのもできるようにした。
(12:16)
http://mono.kmc.gr.jp/~oxy/w/hiki.cgi?ast
そうか、「AST」というライブラリが別にあったんですね。失礼しました。 昨日のぼくの発言は一般名詞の「AST (Abstract Syntax Tree)」のことでした。 Ripper はしばらく RubyForge に入っていたんですが、 そこの lib/ripper にも ast.rb があるんです。
んでこいつの作りがいまいち好きになれない。 特にライブラリとしての名前付け規則に従ってないのがよろしくない。 ファイルが ripper/ast.rb でメインクラス名が Parse::Tree で その他に module Ruby が定義されていて、 一方実際に構文木を作るのは ripper/genast.rb になってる。 もうちょっと整理してほしいんだけどなあ。
(12:22)
キャプションの番号がどこまで行ったかよくわからなくなってきた。
Ripper の仕様を昨日言ったように変更しました。
うーむ、lib/ripper/ast.rb って LGPL なのか。 これだと Ruby にはとりこめないな。
(15:38)
そういえば「Ripperとは何か」について 書いたことがないような気がするので書いておく。 一言で。
Ripper は Ruby プログラムのパーサベアボーンキットだ!
え、ベアボーンキットって知らないなあ。 あれだよあれ、自作パソコンのケースと 電源とマザーボードだけセットにして売ってるやつ。 あとは自分で CPU とメモリと HDD を買って付けるとできあがるんだよ。
Ripper も同じ。 Ruby プログラムのパーサの一番面倒でどーでもいいところが ライブラリ化されていて、あとはおいしいとこだけ作ってやれば パーサができるわけですよ。
もちっとわかってる人向けにわかってる説明をすると、 まず lex と yacc、わかるね? んで Ruby のパーサを lex と yacc で書いたとして、 そのアクション以外を提供してくれるのが Ripper。 あとはアクションを (Rubyで) 書けばいい。
そんでは、Ruby のパーサとしては他にもいろいろあるのに Ripper を選ぶ理由は何だろうか。 それは、Ripper は本物の Ruby との文法の互換性がとても高いからだ。 なぜならばパーサの根幹部分を本物の Ruby と共有しているからである。
なにい、「共有しなくても互換性くらい保てるだろヴァーカ!」だと? ふぅ―――――――ん。 ま、やるのは俺じゃないしね……やるだけやってみればいいんじゃない? その……若い頃に無駄な努力をしてあがくのって大切だと思うし! あ、それから Ruby の文法はどんどん アップデートして (複雑になって) いくことを忘れずにね。
一方 Ripper の弱点は以下のようなところだろう。
御託を並べるのはこのへんにしよう。 具体的に、Ripper ではどんなものが作れるのだろうか。 ようするに特製パーサが作れるわけだから、 Ruby プログラムをテキスト的にいじるものはだいたい何でもできる。 例えば以下のような例が挙げられる。
さて、解説を読んで Ripper を使いたくなったとしよう! どこからダウンロードできんだよーとっとと URL さらしやがれ、 と思うかもしれないが、そういうわけにはいかない。現在のところ、 Ripper を使ってみたかったら ruby の CVS HEAD を自分でインストールする必要がある。
[試験に出るポイント]
なぜ 1.8 がだめかと言うと、yacc の定義するトークン (を表す定数) が Ruby 本体のパーサと完璧に同じでないといけないからだ。 1.9 ではトークンが追加されているので 1.8 に合わせるのはけっこう大変なのである。 とは言え 1.8 の parse.y に独自に ripper extension を追加するよりはずっと簡単だ。
# なんか今日はテンション高いなー俺……
(17:13)
ようやく HDD を換えられた。 買ったのは Seagate の ST3120026A (ATA 120GB)。 二つあった HDD を一つにまとめられてスッキリした。
しかし、Windows だとどんなにでかい HDD でも 平気で一つのパーティションにするんだけど、 Linux だとなぜか細かく区切らないと気がすまないんだよな。
~ % df -h Filesystem Size Used Avail Use% Mounted on /dev/hda5 1.7G 88M 1.5G 6% / none 64M 4.0k 63M 1% /tmp /dev/hda6 16G 2.7G 13G 17% /usr /dev/hda7 18G 7.4G 10G 43% /home /dev/hda8 64G 9.7G 51G 16% /d /dev/hda9 9.2G 2.6G 6.1G 30% /var/backups /dev/hda1 99M 3.5M 90M 4% /boot
げ、/home が小さすぎたか。
(01:51)
Solaris には pmap てコマンドがあって、 プロセスのメモリマップを表示してくれる。 これがいいなーと思ってたんだけど、 Linux の procps をアップデートしたら pmap が入っていた。 そこでさっそくいろいろ実行してみる。 ついでにいろいろなアドレスも表示しとく。
~ % ./src/show-vmmap extern global = 08049bf8 static global = 08049bf4 function static = 08049bf0 function local = bffff608 malloc mem = 40140010 alloca mem = bffff484 main() = 080486d0 printf() = 080485a0 dl sin() = 40251360 1893: ./src/show-vmmap 08048000 4K r-x-- /show-vmmap ← printf() 08049000 4K rw--- /show-vmmap 40000000 76K r-x-- /ld-2.2.5.so 40013000 4K rw--- /ld-2.2.5.so 40014000 4K rw--- [ anon ] 4001b000 8K r-x-- /libdl-2.2.5.so 4001d000 4K rw--- /libdl-2.2.5.so 4001e000 1116K r-x-- /libc-2.2.5.so 40135000 24K rw--- /libc-2.2.5.so 4013b000 1048K rw--- [ anon ] 40248000 128K r-x-- /libm-2.2.5.so 40268000 4K rw--- /libm-2.2.5.so bfffe000 8K rwx-- [ stack ] total 2432K
まあだいたいは明らかっていうかあたりまえなんだけど、 興味を引かれたのが printf のアドレス。 よく見ると libc ではなくて show-vmmap の テキスト領域にあることになっている。
それでは Linux/Alpha はどうかと言うと、
dpw600au:~ % ./show-vmmap extern global = 0000000120011278 static global = 0000000120011274 function static = 0000000120011270 function local = 000000011ffff5f0 malloc mem = 00000200001d8018 alloca mem = 000000011ffff5b8 main() = 0000000120000960 printf() = 00000200000bcb60 dl sin() = 0000020000303e80 (pmapがなかったんで/proc/XXX/mapsから) 000000011fffe000-0000000120000000 rwxp 0000000120000000-0000000120002000 r-xp /home/aamine/c/linuxintro/src/show-vmmap 0000000120010000-0000000120012000 rwxp /home/aamine/c/linuxintro/src/show-vmmap 0000020000000000-000002000001e000 r-xp /lib/ld-2.2.5.so 000002000001e000-0000020000022000 rw-p 000002000002c000-000002000002e000 rwxp /lib/ld-2.2.5.so 000002000002e000-0000020000032000 r-xp /lib/libdl-2.2.5.so 0000020000032000-000002000003e000 ---p /lib/libdl-2.2.5.so 000002000003e000-0000020000042000 rwxp /lib/libdl-2.2.5.so 0000020000042000-00000200001b4000 r-xp /lib/libc-2.2.5.so ← ここ 00000200001b4000-00000200001c2000 ---p /lib/libc-2.2.5.so 00000200001c2000-00000200001d2000 rwxp /lib/libc-2.2.5.so 00000200001d2000-00000200001d8000 rwxp 00000200001d8000-00000200002da000 rw-p 00000200002da000-0000020000364000 r-xp /lib/libm-2.2.5.so 0000020000364000-000002000036a000 ---p /lib/libm-2.2.5.so 000002000036a000-0000020000378000 rwxp /lib/libm-2.2.5.so
libc のテキスト領域にあるように見える。普通だ。 もしかして位置独立だとこうなるのかなー (Alpha はデフォルトで PIC だから) と思って i686 のほうを -fPIC でコンパイルしてみたんだけど、 変化はないようだ。
ふーん。何かスタブが入ってるのかねえ。 OK, ディスアセンブルだ。
% objdump -d show-vmmap (略) 080484f0 <.plt>: 80484f0: ff 35 90 9b 04 08 pushl 0x8049b90 80484f6: ff 25 94 9b 04 08 jmp *0x8049b94 80484fc: 00 00 add %al,(%eax) 80484fe: 00 00 add %al,(%eax) 8048500: ff 25 98 9b 04 08 jmp *0x8049b98 8048506: 68 00 00 00 00 push $0x0 804850b: e9 e0 ff ff ff jmp 80484f0 <_init+0x28> 8048510: ff 25 9c 9b 04 08 jmp *0x8049b9c 8048516: 68 08 00 00 00 push $0x8 804851b: e9 d0 ff ff ff jmp 80484f0 <_init+0x28> 8048520: ff 25 a0 9b 04 08 jmp *0x8049ba0 8048526: 68 10 00 00 00 push $0x10 804852b: e9 c0 ff ff ff jmp 80484f0 <_init+0x28> 8048530: ff 25 a4 9b 04 08 jmp *0x8049ba4 (略) 8048596: 68 48 00 00 00 push $0x48 804859b: e9 50 ff ff ff jmp 80484f0 <_init+0x28> 80485a0: ff 25 c0 9b 04 08 jmp *0x8049bc0 ← ここに飛ぶ 80485a6: 68 50 00 00 00 push $0x50 80485ab: e9 40 ff ff ff jmp 80484f0 <_init+0x28> 80485b0: ff 25 c4 9b 04 08 jmp *0x8049bc4 80485b6: 68 58 00 00 00 push $0x58
printf は 080485a0 なので、いきなり jmp 命令か。 えーと、
jmp *0x8049bc0
って何だろ。あ、そうか、 「ポインタ 0x8049bc0 をデリファレンスしたアドレスに飛ぶ」だな。 0x8049bc0 は show-vmmap の BSS 領域だから、 ld.so がここに printf 本体のアドレスを置いてるんだろう。 OK, 自力で printf 本体をゲットするぜ。
こんな感じかな?
~ % cat x86resolve.c #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { typedef int (*printf_t)(const char *fmt, ...); printf_t myprintf; char *p; p = (char*)printf; p += 2; /* skip instruction */ printf("BSS address = %08lx\n", (unsigned long)(*(long*)p)); myprintf = (printf_t)(*(long*)(*(long*)p)); printf("printf() body = %08lx\n", (unsigned long)myprintf); myprintf("test %s\n", "OK"); exit(0); } ~ % ./x86resolve BSS address = 08049648 printf() body = 4006f8ec test OK
おおっ。うまくいってしまった!
というか .plt って何の略やねん。 …… ld.so(1) によると Procedure Linkage Table らしい。 なるほどね。
このへんまで書いたところでずっと詳しいページを見付けた。
こうなると問題にすべきは 「なんで x86 では libc のテキスト領域を指していないか」 じゃなくて、 「なんで Alpha では libc のテキスト領域を指しているか」 のような気がするな。
……
そうか。 Alpha は最初からグローバルポインタテーブルを使ってるせいだ。 もともと間接参照してるんだからもう一段入れる必要はない。
(01:57)
なぜわたしは突然 Alpha アセンブラを書き始めているのでしょうか
.file 1 "put.c" .set noat .set noreorder .section .rodata $LC0: .ascii "Hello, World!\12\0" .text .align 5 .globl main .ent main main: .mask 0x4000000,-16 ldah $29,9($27) lda $29,4896($29) $main..ng: lda $30,-16($30) lda $17,$LC0 lda $16,1 lda $18,15 lda $0,4 callsys stq $26,0($30) .prologue 1 mov $31,$16 lda $16,3 lda $0,1 ldq $26,0($30) nop lda $30,16($30) ret $31,($26),1 .end main
だめだ落ちる……。 とりあえず gcc のインラインアセンブラから始めて _exit(2) は呼べるようになったんだけど、 write(2) を呼ぼうとした瞬間に落ちた。 gp (global pointer) にまともな値が入ってないから、 リテラル (BSS) を参照した瞬間に落ちるようだ。
くそう、BSS がだめならスタックに積んでくれるわ!
~ % cat put.s .set noreorder .text .globl main .ent main main: lda $16,10 ; a0 ← '\n' stb $16,0($30) ; push a0 lda $16,97 ; a0 ← 'a' stb $16,-1($30) ; push a0 subq $30,1,$17 ; a1 ← sp lda $16,1 ; a0 ← 1 (STDOUT_FILENO) lda $18,2 ; a2 ← 2 lda $0,4 ; v0 ← 4 (NR_write) callsys lda $16,7 ; a0 ← 7 lda $0,1 ; v0 ← 1 (NR_exit) callsys .end main ~ % gcc -static -nostdlib -Wl,--entry=main put.s -o put ~ % ./put a ~ % echo $? 7
うごいた!
(02:59)
HSP とかが goto メインなのはセーブ・ロードの実装が 簡単だからじゃないのかなあ。 吉里吉里 (のKAG) では if の中にラベルを書けない (セーブポイントにできない) っていう制限があるけど、 あれもつまりそういうことだろう。 ゲームプラットフォームとして、if/while を取るか セーブ機構を取るかという選択ならセーブ機構になるよね。
(00:43)
ツッコミ欄は狭いのでこっちにします。
> 自分でセーブ機能を実装するってこと? > システムがその時点のスナップショットを > 取っておく機能を持つってのはあるような > 気がしますから、あんまり関係ないような > 気がします。
うーん、うまく説明できるかな。 例えばありがちなビジュアルノベルのスクリプトを例にすると、 次のように書いたとして (微妙に Ruby 風)、
a = true if フラグチェック 背景1表示 立ち絵1表示 テキスト1…… b = true <<<セーブポイント1>>> 背景2表示 立ち絵2表示 テキスト2…… end
セーブポイント 1 でセーブして別プロセスでロードしたときに、
の状態でセーブポイント 1 から始められる、というのが理想です。
しかし例えば Ruby で上記のように書いたとすると、 データは特定のオブジェクトのインスタンス変数で妥協すればなんとかなりますが、 if 文の途中にすっとんでいけません (callcc ではプロセスをまたげない)。
また KAG3 でも上記のような書きかたは禁止されており、 次のように書かないといけません (文法は違います)。
フラグ a = true if フラグチェック 背景1表示 立ち絵1表示 テキスト1…… フラグ b = true end <<<セーブポイント1>>> if フラグチェック 背景2表示 立ち絵2表示 テキスト2…… end
これが「if の中にはラベルを書けない」という制限です。 その代わり、この制限にのっかっていれば goto ラベルを書いとくだけで セーブ・ロードを処理系が面倒見てくれます。 変数にも「勝手に保存される変数」と「ゲーム終了ごとに中身が消える変数」 があって、「勝手に保存される変数」はロードすると勝手に復帰します。
それでは、if 文 (やサブルーチン呼び出し) の中に セーブポイントを置けないもんでしょうか。 できないことはないでしょうが、関数呼び放題、if/while 使い放題の言語で 普通に continuation を保存してしまうとデータの融通が効きません。 例えばちょっとスクリプトが変わっても同じセーブデータを使いたいとか、 どこでセーブしたのか情報を取り出したいとか。 それだったらちょっとくらい制御構造を妥協してもいいんじゃないか、 と言いたいのです。
まとまらねえな。
(04:25)
■
ささだ [自分でセーブ機能を実装するってこと? システムがその時点のスナップショットを取っておく機能を持つってのはあるような気がしますから、あんまり関係ないような気がします。
if の中でラベルを書けないっていうのは、よくわからないなぁ。なんか根本的な勘違いしてそうですけど。]
■
ささだ [注意しとくと、勘違いってのは、私がって意味ですね。
フラグとかのデータについては、あんまり関係ないですね。それはいいとして。背景データとか、その辺も、同じデータ構造の問題になりますね。
if文で駄目な理由がやっぱりわからないなぁ。例えば自分が処理系を作ったとして、どうしてそこで詰まるのかが思い浮かびません。保存しなきゃいけない状態は、トップレベルと変わらないような気がして。
もちろん、関数コールの途中とかだと、スタックの保存とか考えなくてはならなくて、面倒ってのは散々味わいました(え?)けど、if文の途中ってのが、ちと想像できません。
考えている、要求と制限で、色々と前提が変わっていて、話が合ってないだけな気もします。
ゲーム開発をわかりやすく(シンプルに)するための制限、として if文中でのラベルなしってのはあるかもしれないけれど。システムとして、そこに課題があるんだろうか。
なんか、私の知らない「状態」が存在したりするのかも。]
ああそうかわかった、やっぱごちゃごちゃに書きはじめたのがまずかったんだ。
まず、「if の途中にラベルがある『から』ダメ」というのはぼくの勘違いです。 ちょっと自分で混乱して話を混ぜてました。 if の途中でセーブしたって復帰できねえじゃん、 というのは Ruby に関してだけです (もちろん、工夫しなきゃトップレベルでもだめだけど)。 最初は Ruby と HSP の比較で書いてたもんで。
あともう一つは、C みたいに if でスコープが積まれる仕様を 知らないうちに想定してたせいです。 if の中で b に代入してるのはそのへんを表現したかったから。
で、サブルーチンがあって if/while があってローカルスコープがあっても ちゃんと保存すれば完全に復帰「しようと思えばできる」というのはわかってます。 ソースコード変更に追従「しようと思えばできる」のもわかります。 言いたかったのは「できるか」じゃなくて、 そこらへんの事情を考えればいまどき goto バリバリな言語を作るのも 開発者のトレードオフの範囲だろう、ということです。
うーん、やっぱ吉里吉里の話を出したのはよくなかったな。 Ruby と HSP だけなら話は早かったんだ。
(14:07)
というか、goto について語りあう前に俺は原稿を書くべきです。
ああ、このままでは夏休みが終わってしまう。 まだ宿題のプリントが終わってないのに。
……俺はしょうがくせいか。
(15:47)
http://sourceforge.net/projects/flexwiki/
MS 謹製 Wiki エンジンだってさ。 ダウンロードしてみると C# で書いてあることがわかる。 誰か動かしてる人いないかな〜
(23:29)
全然書けてないと思ってた hier の章が実は意外に埋まっててラッキー、 と思いつつも中間演習 (grepを作る) の章は全然終わってないし ましてや HTTP サーバの章はスカスカなのであり 絶対に今日の 24:00 には間に合わないっていうか すでに過ぎてるわけだから間に合うほうがおかしいっていうか、 そんな感じの気分です。
(02:08)
■
*namu* [ソーセージ…参考になりました。
お大事に。]
■
通りすがれ [bitchannel を co しようとしたらエラーになってしまいます orz
cvs update: failed to create lock directory for `/src/bitchannel/test' (/src/bitchannel/test/#cvs.lock): Permission denied]
■ ささだ [どこを縦読みするんでしょうか。]
■ anonyomous [9をたん]
■ 青木 [16文字目あたりかな]
■ 青木 [あ、BitChannelのチェックアウトエラーだけはこっそり直しておきました。]
Copyright (c) 2002-2007 青木峰郎 / Minero Aoki. All rights reserved.
■ anonyomous [怖くて試せませんが、
>~/bin/df
で上書きすれば間に合えば止まるかも。]
■ 青木 [ああっ、そうか!
コマンドのほうを変えればいいんですね。
それは思いつかなかった……。]
■ なかだ [^Zでsuspendでは?]
■ 青木 [そっか。それでもうまくいきました。
けっこういろいろあるなあ。]
■ Nayukist [今更ですが予めユーザーの最大プロセス数を制限しとくのがいいような。]
■ kjana [*BSD なら
Terminate the process group with pgid 117:
kill -- -117
なんて出来るようですが。
もしくは慌てず root のシェルを確保しておく。root のプロセス枠がいくつか確保されてるはずだから。]