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 にあるんだけども、処理の意味が良くわからなかった。

Upgrade an SV to a more complex form. Generally adds a new body type to the
SV, then copies across as much information as possible from the old body.
You generally want to use the C macro wrapper. See also C.

perl_parse(my_perl, NULL, argc, argv, (char **)NULL); を深追い

parse というくらいだから構文解析をしています。ここでは構文解析本体の parse_body の呼び出しとコマンドラインの引数の前処理をしているだけ。perl_parse からの重要な処理の流れをココで書いておくと、

perl_parse => parse_body => yyparse => yylex

ってなってます。まずその流れを掴むことすら難しかったです。
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 が字句解析部分で実行されていることまで解析できたし、息が切れてきたのでココでいったん深追いはおしまい。結構ウソ書いてる可能性アリ・・・(^^ゞ

疲れた・・・

- スポンサーリンク -