とりあえず 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)
はじめまして。AST作者の吉田(HN:oxy)と申します。
このプログラムは自分の目的を果たすためにとりあえず書いたもので、場当たり的な所が一杯あるのは自覚しているのですが、それでも公開すれば誰かの助けになるかもしれないと公開したものです。
ですから、もしご不満があれば直しますので、今回の日記の最後の行の真意を教えていただけないでしょうか。
え? いや、たぶんそれは違うライブラリじゃないかと思います。
ぼくが言っているのは RubyForge のレポジトリに入っているやつでした。
ちなみに RubyForge のほうに関して思ったのは、「名前を合わせて
ほしかった」というただそれだけです。
わざわざほのめかすような書きかたをしてすみませんでした。
↑「RubyForgeにある、Ripperのレポジトリに入っているやつ」
(lib/ripper/ast.rb) のことです。メンテしているのは
mark sparshatt で、オリジナルは別にあったはず。