Perl の内部処理系をお勉強
ちょっと前に perlfilter - Source Filters - についてお勉強したときから調べようと思っていたことなのですが、Perl の内部処理の流れ(Perl 5 Internals)についてお勉強中です。思いっきり見逃してしまいましたが、Perl 5 Internals に興味を満ち始めたこのタイミングにあわせたかのように Shibuya Perl Mongers : Shibuya Perl Mongersテクニカルトーク#9 で XS ネタをやっていたみたいです。残念です。
さて、今回のお勉強の方法は、ズバリ perl 5.8.8 のソースの読解です。もちろん今回は流れを把握するためのものなので、あまり樹海の奥深くまで足を踏み入れたくはないのですが・・・。この記事を書いている時点では perl_parse までの流れを読み終えたところなのですが、構文解析と字句解析が樹海でした。
さて、とっかかりとしたのは perlembed - perldoc.perl.org です。Perl の処理系を組み込むためのドキュメントです。Perl の処理系を組み込んだ一番小さなプログラムは、Perl をインストールしてくると付属でコンパイルされる miniperl です。miniperl のような必要最低限の perl インタプリタの実行環境を作るには、これだけの記述が必要になります。
#include/* from the Perl distribution */ #include /* from the Perl distribution */ static PerlInterpreter *my_perl; /*** The Perl interpreter ***/ int main(int argc, char **argv, char **env) { PERL_SYS_INIT3(&argc,&argv,&env); my_perl = perl_alloc(); perl_construct(my_perl); PL_exit_flags |= PERL_EXIT_DESTRUCT_END; perl_parse(my_perl, NULL, argc, argv, (char **)NULL); perl_run(my_perl); perl_destruct(my_perl); perl_free(my_perl); PERL_SYS_TERM(); }
と言うわけで、順にこれらの関数を追っていくことにしました。
まずは、頻繁に閲覧する source file はこいつらでした。
perl.c / perly.c / sv.c / toke.c / mg.c / scope.c
perl.h / embedvar.h / embed.h / sv.h / perlapi.h
さて、では順にソースコードを追っていきます。処理の流れがわかるように、重要な部分の関数呼び出しだけをピックアップしていきました。
my_perl = perl_alloc(); を深追い
一言で言うとインスタンス生成(メモリ空間確保)をしているところ。
perl.c:213 PerlInterpreter * perl_alloc(void) { PerlInterpreter *my_perl; my_perl = (PerlInterpreter*)PerlMem_malloc(sizeof(PerlInterpreter)); INIT_TLS_AND_INTERP; return (PerlInterpreter *) ZeroD(my_perl, 1, PerlInterpreter); }
perl_construct(my_perl); を深追い
一言で言うとインスタンスの初期化をしているところ。
perl.c:237 void perl_construct(pTHXx) { ・・・ 〜 ・・・ init_interp(); ・・・ 〜 ・・・ init_stacks(); init_ids(); ・・・ 〜 ・・・ init_i18nl10n(1); ・・・ 〜 ・・・ sys_intern_init(); PerlIO_init(aTHX); /* Hook to IO system */ ・・・ 〜 ・・・ Perl_reentrant_init(aTHX); ・・・ 〜 ・・・ }
途中で sv_upgrade() って関数がでてくるわけんですけど、実体は sv.c にあるんだけども、処理の意味が良くわからなかった。
SV, then copies across as much information as possible from the old body.
You generally want to use the C
perl_parse(my_perl, NULL, argc, argv, (char **)NULL); を深追い
parse というくらいだから構文解析をしています。ここでは構文解析本体の parse_body の呼び出しとコマンドラインの引数の前処理をしているだけ。perl_parse からの重要な処理の流れをココで書いておくと、
ってなってます。まずその流れを掴むことすら難しかったです。
perl.c:1433 int perl_parse(pTHXx_ XSINIT_t xsinit, int argc, char **argv, char **env) { ・・・ 〜 ・・・ なんか argv[] のイレギュラー処理がいろいろと書いてあるっぽい。 ・・・ 〜 ・・・ switch (ret) { case 0: parse_body(env,xsinit); if (PL_checkav) call_list(oldscope, PL_checkav); ret = 0; break; ・・・ 〜 ・・・ }
parse_body(env,xsinit); の深追い
構文解析本体かと思いきや、ここでの主な処理内容はスクリプトのファイルの内容の読込。ここからさらに構文解析を行う yyparse() を呼び出す。
perl.c:1627 STATIC void * S_parse_body(pTHX_ char **env, XSINIT_t xsinit) { ・・・ 〜 ・・・ init_main_stash(); ・・・ 〜 ・・・ perl コマンドのオプション処理分岐 ・・・ 〜 ・・・ init_perllib(); open_script(scriptname,dosearch,sv); ※ここでやっとscript-fileがreadされる ・・・ 〜 ・・・ boot_core_PerlIO(); boot_core_UNIVERSAL(); boot_core_xsutils(); ・・・ 〜 ・・・ init_lexer(); ※字句解析前の初期化 ・・・ 〜 ・・・ if (yyparse() || PL_error_count) { ・・・ 〜 ・・・ }
yyparse() を深追い
この関数こそ、Perl の構文解析器の本体です。この部分から烈しく樹海。構文解析のルールは perly.y で定義されているっぽい。この関数からさらに字句解析を行う yylex() が呼び出される。正直、それ以上は何やっているか理解できませんわ。
perly.c:1412 int yyparse() { ・・・ 〜 ・・・ yyloop: ・・・ 〜 ・・・ 構文解析部分。ここから yylex() が呼び出されている。 ・・・ 〜 ・・・ goto yyloop; ・・・ 〜 ・・・ }
yylex() を深追い
樹海のように深い。とにかくココで字句解析。再帰的に1文字ずつ読み取っては token 解析を繰り返す。このフェーズで、eval と source filters の処理は行われているっぽい。るっぽいと書いているのは、完全に理解できなかったから。
toke.c:2399 int Perl_yylex(pTHX) { ・・・ 〜 ・・・ switch (PL_lex_state) { ・・・ 〜 ・・・ } ・・・ 〜 ・・・ retry: switch (*s) { default: if (isIDFIRST_lazy_if(s,UTF)) goto keylookup; ・・・ 〜 ・・・ case 0: ・・・ 〜 ・・・ if (!PL_in_eval && !PL_preambled) { ・・・ 〜 ・・・ } do { ・・・ 〜 ・・・ eval と フィルター処理はココだと思われるんだが・・・ ・・・ 〜 ・・・ } while (PL_doextract); ・・・ 〜 ・・・ ※この辺もう息切れしたので余り解析できていない・・・ goto retry; ・・・ 〜 ・・・ ※この辺もう息切れしたので余り解析できていない・・・ case '\r': ・・・ 〜 ・・・ case '#': case '\n': ・・・ 〜 ・・・ 以下同様に、ここで terminate 系の字句解析が行われる。:, {, $ あたりの字句解析が込み入ってる。 ・・・ 〜 ・・・ case 'z': case 'Z': keylookup: { ・・・ 〜 ・・・ 以下同様に、ここで keyword 系の字句解析が行われる。 ・・・ 〜 ・・・ } reserved_word: switch (tmp) { default: /* not a keyword */ just_a_word: { ・・・ 〜 ・・・ プログラム中に出現するプログラム的な意味を持たない単語の処理 ・・・ 〜 ・・・ } case KEY___FILE__: ・・・ 〜 ・・・ case KEY___LINE__: ・・・ 〜 ・・・ 以下同様に、特別な意味を持つ単語の処理が続く ・・・ 〜 ・・・ case KEY_y: s = scan_trans(s); TERM(sublex_start()); } }} }
とりあえず、当初の目標の perlfilter が字句解析部分で実行されていることまで解析できたし、息が切れてきたのでココでいったん深追いはおしまい。結構ウソ書いてる可能性アリ・・・(^^ゞ
疲れた・・・
コメントやシェアをお願いします!
drk
自分用のメモですが、yyparse() 周りの説明が以下にありました。
http://d.hatena.ne.jp/dayflower/20080701/1214898904
drk
詳しい補足を有り難うございます。perlの中の世界は思った以上に難解で、皆さん凄いなぁ〜と思う次第です。勉強させて頂きます。
sv_upgrade
sv_upgradeは、svという基本型を基本型を保ちつつ、それを応用した型へ移行させるための関数です。要するに継承みたいです。perlのすべての型の基本はsvです。文字列も配列もハッシュもサブルーチンもI/Oハンドルも、すべて基底クラスはsvです。それぞれの型は固有番号を持っていて、一覧はsv.hのenum svtypeにあります。型の詳しい構造はPerlGuts Illustratedに詳しく図解されています。http://www.perl.org/tpc/1998/Perl_Language_and_Modules/Perl%20Illustrated/。svtypeの番号は0(SvNULL)が基本で、upgrade後には番号が増えます。それにともなって内部の構造も複雑になっていきます。現在のsvがどの番号かを見るときは、svに対してSvTYPEを施すことで見れます。番号以外に構造を見るときは、svに対してsv_dumpを施してやるとよいです。