使っている共用体メンバがidだから型はIDである。それを後で区別するために何の自己代入かを値として渡すわけだ。自己代入をまとめるかというと、そのほうが規則が短くなるからだ。順位が違うからである。まわりの状況でトークンの切り方が変わったりする。コードは空白で挙動が変わる一例だ。tSYMBEG;(parse.y)今度もlex_state関係は無視してspace_seenまわりだけに注目してほしい。space_seenはトークンの前に空白があると真になる変数だ。ければtCOLON2になるらしい。長いものも見てみることにする。識別子のスキャンパターンだ。yylexの全体像はこうなっていた。識別子のスキャンコード}次のコードは巨大switchの末尾のところからの引用である。長いのでコメントを入れつつ示そう。tokfix();(parse.y)最後に!/?を追加しているところの条件に注目してほしい。この部分は次のような解釈をするためである。ない)つまり最長一致「ではない」。ときには破っても構わないのだ。実際の記号を割り出している。それはまあいいのだが、その中にちょっと変わった項目がある。とめてスキャンしておいて後から分類するほうが効率的なのである。if文とstrcmp()で比較しまくる方法がある。しそれでは全くもって賢くない。スピードも線型増加する。普通はリストとかハッシュにデータだけ分離して、コードを短く済ますだろう。tok());}それでrubyはどうしているかと言うと、ハッシュテーブルを使っている。関数を作れることがある。わかってい」るわけだから、完全ハッシュ関数が作れそうだ。「作れる」のと実際に作るのとは別の話だ。手動で作るなんてことはやっていられない。するのだから、そういう作業は自動化しなければいけない。そこでgperfである。ハッシュ関数を作ってくれる。もらうとして、ここでは生成した結果の使いかただけ述べよう。rubyでのgperfの入力ファイルはkeywordsで出力はlex.cである。parse.yはこれを直接#includeしている。staticにすべきなのだ。そのlex.cにはrb_reserved_word()という関数が定義されている。予約語のchar*をキーにしてこれを呼ぶと索ける。kwtable*が返る。kwtableの定義は以下のとおり。(kIF_MODなど)。lex_stateは「その予約語を読んだ後に移行すべきlex_state」である。lex_stateについては次章で説明する。実際に索いているのはこのあたりだ。{(parse.y)文字列類yylex()のダブルクォート(")のところを見ると、こうなっている。tSTRING_BEG;(parse.y)なんと一文字目だけをスキャンして終了してしまう。規則を見てみると、次の部分にtSTRING_BEGが見付かった。:この規則は文字列中の式埋め込みに対応するために導入された部分である。tSTRING_CONTENTがリテラル部分、tSTRING_DBEGが"#{"である。tSTRING_DVARは「後に変数が続く#」を表す。".....#$gvar...."のような構文である。場合には{と}は省略できるのだ。ちなみにDVAR、DBEGのDはdynamicの略だと思われる。term_pushは「アクションのための規則」である。ここでyylex()に戻る。わけだから、次のyylex()でいきなり変数だのifだのスキャンされたら困る。tSTRING_BEG;......lex_strtermだ、ということになる。yylex()の先頭に戻ってみよう。0にしておかなければならない。それをやっているのが次の部分だ。(事実上のスタックプッシュ)、通常のアクションで復帰している(ポップ)。なんでこのような七面倒くさいことをするのだろう。そこに実は問題がある。これはよく知られたyaccの制限である。yylvalがグローバル変数なので、うかつに再帰すると値が壊れてしまう。使うことが多いのでbisonが前提になるとちょっと面倒だ。ないかを表すわけだが、実は内容にも意味がある。*lex_strterm;(parse.y)まずこの定義から型はNODE*だとわかる。第12章『構文木の構築』で詳しく説明する。(paren))(parse.y)これはlex_strtermに格納するノードを作るマクロだ。"文字列ならば"である。'文字列ならば'である。parenは%文字列のときに対応する括弧を格納するのに使う。%Q(..........)ならparenに'('が入る。termには閉じ括弧')'が入る。%文字列以外のときはparenは0だ。最後にfuncだが、文字列の種類を示す。使える種類は以下のように決められている。string_typeのそれぞれの意味は次のようだとわかる。%W文字列スキャン関数あとは文字列モードのときのyylex()、つまり冒頭のifを読めばいい。}(parse.y)大きくヒアドキュメントとそれ以外に分かれている。parse_string()は読まない。いスパゲッティコードになっている。ままじゃん!」という文句が出ること必至だ。おく\footnote{parse_string()の解析:添付CD-ROMdoc/parse_string.html}。興味のある読者はそちらを眺めてみてほしい。ヒアドキュメント普通の文字列に比べるとヒアドキュメントはなかなか面白い。要素と違って単位が「行」であるせいだろう。途中に置けるところが恐ろしい。するyylex()のコードから示そう。token;(parse.y)例によってlex_stateの群れは無視する。読んでおり、残りはheredoc_identifier()でスキャンするらしいとわかる。そこでheredoc_identifier()だ。}(parse.y)開始記号(<<EOS)を読むところはどうでもいいのでバッサリ省略した。ここまでで入力バッファは図10のようになっているはずだ。入力バッファは行単位だったことを思い出そう。n)"のスキャンheredoc_identifier()でやっていることは以下の通り。(A)lenは現在行のうち読み込んだバイト数だ。(B)そしていきなりlex_pを行の末尾まで飛ばす。この残りの部分はいつパースするのだろう。len(既に読んだ長さ)を保存しているところにヒントがある。heredoc_identifier()前後の動的コールグラフを以下に簡単に示す。here_documentそしてこのhere_document()がヒアドキュメント本体のスキャンを行っている。以下に、異常系を省略しコメントを加えたhere_document()を示す。tSTRING_CONTENT;}rb_str_cat()はRubyの文字列の末尾にchar*を連結する関数だ。(A)では現在読み込み中の行lex_lastlineをstrに連結している。連結したらもう今の行は用済みだ。見せかけつつ実は次の「行」を読み込んでいるのだ。(C)でlex_pは次の行に移ることになる。最後にdo〜whileループを抜けてheredoc_restore()だ。}(parse.y)here->nd_origには開始記号のあった行が入っている。here->nd_nthには開始記号のあった行のうち既に読んだ長さが入っている。いうわけだ(図11)。(書籍紹介ページへ飛びます)。