history

青木日記 RSS

<前の日 | この月 | 次の日>

2004-09-20

ripper (1)

とりあえず ripper のパッチだけとりこんじゃうかな。

rb_reserved_word は static だとずっと思い込んでた。 よく見たら extern なんだな。

よくよく考えると、 本体と Ripper のトークンはバイナリ的に同じじゃなきゃだめなんだから、 別々の yacc で処理するだけでも危険なんだ。 そういう意味では本体も bison 必須になったのは都合がいい。

lex_strterm の件は迷う。 せっかく本体が再入可能になったことだし、 yyparse の再帰で処理できないもんだろうか。

(06:11)

ripper (2)

パッチをあてたところ

ヒアドキュメントまわりで

lex_p がふしぎなおどりをおどっている!

MP を 28 吸い取られた!

(06:12)

ripper (3)

parser->buf の役割をやっと思い出した。 Ruby レベルではトークンが tokbuf に保存されるから 入力バッファを捨てられるけど、 Ripper では行をまたぐトークンがあるからまずいんだ。

あと、ヒアドキュメントのまわりで current_position を退避・復帰してないのもまずい。 これもやはり入力バッファから書き直したほうがよさそう。

やっぱり入力バッファは Ripper 専用に書き直すしかないな。

(06:37)

(14:28 追記) やっぱやめた。

ripper (4) #pos

がんばって current_position を退避・復帰するか Ripper#pos を捨てるかの選択を迫られた結果、 あっさり Ripper#pos を捨てることに大決定。 ま、lineno と column があるんだからなんとかなるでしょ。

(13:12)

ripper (5) 複数行の文字列類

次はこれか……。

~/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)

ripper (6) yylval

しまったあああああああああ! スキャナで dispatch した値をパーサに伝えてない! うっわー、これは致命的。 値を渡すことを考えるとトークンを並べなおすなんて不可能だ。 パーサイベントも遅延させるか……だめだ……無理すぎる……。

いや待てよ、遅延スキャナイベントですらまずいな。 一回パーサに制御を戻さないことには yylval を拾ってくれない。 つまり無理矢理でも一つのトークンにまとめないとだめだ。

うわー……最悪だ……。 まさかここまでバカやってるとは思わなかった。

(15:47)

ripper (7) sexp

こんなものを作ってみたのだが……

% 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)

ripper (8)

今日だけで 13 回もコミットしてしまった……。 もうちょっと考えよう。

(18:28)

ripper (9) lib/**/*.rb

lib 以下のライブラリを片端からパースして再現できるかどうか試してみた。

まあ当然と言うかやっぱりと言うか、diff が出るね。

  • コメントまわりの空白がおかしい
  • __END__ 以降が捨てられてる

後者は気付いてたし、直すのも簡単。

前者は何だろうなあ。また yylex をぐるぐるまわらないといけないのか……。 でもさすがに疲れたし、原稿書きに戻りたいんで、続きはまたこんど。

しかし、これ以外に何も出ないというのは結構収穫だ。 SEGV もしなかったしな (といっても SEGV しやすいのはパースイベントで、 今回のテストはパースイベントを使っていないのであまり当てにはならない)。

(18:41)

ripper (10) usage

そーいや Ripper の使いかたって書いたことなかったんでちょっとだけ書いとく

基本

# けーしょーします
class MyParser < Ripper
  # イベントハンドラをていぎしてみます
  def on__comment(tok)
    print tok
  end
end
 
# きどうします
MyParser.new(ARGF).parse

いじょ

一歩進んでみる

  • イベントにはスキャナイベントとパーサイベントの二種類がある!
  • イベントを拾うメソッドがイベントハンドラ (event handler)
  • すべてのイベントハンドラは on__XXXX という名前
  • スキャナイベントは引数一個だ
  • パーサイベントの引数の数はイベントによる。Ripper::PARSER_EVENT_TABLE を見よ
  • あらゆるスキャナイベントをまとめて拾うことのできる on__scan がある。 on__scan だけは引数が二個で def on__scan(event,token) になっている。 event は :on__XXXX の形式で、token はそのまんま。
  • 全パーサイベントのリストが Ripper::PARSER_EVENTS に入ってる ("on__" は付いてない)
  • 全スキャナイベントのリストが Ripper::SCANNER_EVENTS に入ってる ("on__" は付いてない)
  • Ripper を継承すると ruby がスキャンするそのままの順番で スキャナイベントが起こるので、ヒアドキュメントの前後でややこしいことになる。 トークンが文字列通りの順番で欲しいときは Ripper::Tokenizer を継承しろ!
  • スキャナイベントハンドラでは行番号と桁が有効になる。 行番号は lineno()、桁は column()
  • パーサイベントハンドラでは行も桁も当てにならないのでトークンに情報を付けておくこと
  • 構文木を作るインターフェイスは作ってる人がいるのでちょっと待つといいことがある。 でもあの AST は……いやなんでもない

(19:23)

本日のツッコミ(全3件) [ツッコミを入れる]
oxy (2004-09-21 09:45)

はじめまして。AST作者の吉田(HN:oxy)と申します。
このプログラムは自分の目的を果たすためにとりあえず書いたもので、場当たり的な所が一杯あるのは自覚しているのですが、それでも公開すれば誰かの助けになるかもしれないと公開したものです。
ですから、もしご不満があれば直しますので、今回の日記の最後の行の真意を教えていただけないでしょうか。

青木 (2004-09-21 09:52)

え? いや、たぶんそれは違うライブラリじゃないかと思います。
ぼくが言っているのは RubyForge のレポジトリに入っているやつでした。
ちなみに RubyForge のほうに関して思ったのは、「名前を合わせて
ほしかった」というただそれだけです。
わざわざほのめかすような書きかたをしてすみませんでした。

青木 (2004-09-21 09:56)

↑「RubyForgeにある、Ripperのレポジトリに入っているやつ」
(lib/ripper/ast.rb) のことです。メンテしているのは
mark sparshatt で、オリジナルは別にあったはず。

名前
メールアドレス

<前の日 | この月 | 次の日>
2002|04|05|06|07|08|09|10|11|12|
2003|01|02|03|04|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|04|05|