[[Pythonを読む]]

#contents

*PyParser_ParseFileObject (Parser/parsetok.c) [#l2c3f89c]

ファイルを指定したときに呼び出されるPyParser_ParseFileObject からスクリプト解析を見ていきます。(ファイルを指定しないでpythonコマンドを起動したときも呼ばれる)

#code(C){{
node *
PyParser_ParseFileObject(FILE *fp, PyObject *filename,
                         const char *enc, grammar *g, int start,
                         const char *ps1, const char *ps2,
                         perrdetail *err_ret, int *flags)
{
    struct tok_state *tok;

    if (initerr(err_ret, filename) < 0)
        return NULL;

    if ((tok = PyTokenizer_FromFile(fp, enc, ps1, ps2)) == NULL) {
        err_ret->error = E_NOMEM;
        return NULL;
    }
#ifndef PGEN
    Py_INCREF(err_ret->filename);
    tok->filename = err_ret->filename;
#endif
    return parsetok(tok, g, start, err_ret, flags);
}
}}

*parsetok (Parser/parsetok.c) [#rdce3b52]

parsetok関数は長いです。エラー処理をばさっと削ると以下のような感じ。

#code(C){{
static node *
parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
         int *flags)
{
    parser_state *ps;
    node *n;

    ps = PyParser_New(g, start);
    for (;;) {
        char *a, *b;
        int type;
        size_t len;
        char *str;
        int col_offset;

        type = PyTokenizer_Get(tok, &a, &b);
        len = b - a; /* XXX this may compute NULL - NULL */
        str = (char *) PyObject_MALLOC(len + 1);
        if (len > 0)
            strncpy(str, a, len);
        str[len] = '\0';

        if (a >= tok->line_start)
            col_offset = Py_SAFE_DOWNCAST(a - tok->line_start,
                                          Py_intptr_t, int);
        else
            col_offset = -1;

        if ((err_ret->error =
             PyParser_AddToken(ps, (int)type, str,
                               tok->lineno, col_offset,
                               &(err_ret->expected))) != E_OK) {
            if (err_ret->error != E_DONE) {
                PyObject_FREE(str);
                err_ret->token = type;
            }
            break;
        }
    }

    if (err_ret->error == E_DONE) {
        n = ps->p_tree;
        ps->p_tree = NULL;
    }

    PyParser_Delete(ps);

done:
    PyTokenizer_Free(tok);

    return n;
}
}}

パーサを作り、トーカナイザからトークンを取得して追加、終わりまで来たらループ終了、(エラーがなかったら)ノードができているのでそれを返すということをしています。

**PyParser_New (Parser/parser.c) [#bbe749ea]

#code(C){{
PyParser_New(grammar *g, int start)
{
    parser_state *ps;

    if (!g->g_accel)
        PyGrammar_AddAccelerators(g);
    ps = (parser_state *)PyMem_MALLOC(sizeof(parser_state));
    ps->p_grammar = g;
    ps->p_tree = PyNode_New(start);
    s_reset(&ps->p_stack);
    (void) s_push(&ps->p_stack, PyGrammar_FindDFA(g, start), ps->p_tree);
    return ps;
}
}}

DFAとあります。つまり、スクリプトからノードに変換するにはオートマトンを使っているようです。yaccを使っていないのはインデントが意味を持っているから?

grammer、その実体は_PyParser_GrammarはPython/graminit.cに定義されています。もっともDFAを表現したデータが格納されているだけなのでこれだけ見てもよくわかりません。graminit.c(とInclude/graminit.h)はParserにあるpgenを用いて生成されているようです。また、pgen自体の入力ファイルはGrammar/Grammarです。これを見るとPythonの文法が書かれています。

**PyParser_AddToken (Parser/parser.c) [#be144651]

PyParser_AddTokenではトークンと現在の状態(どの文法要素を解析中か)からノードを構築しています。書いてある処理ははっきり言って難解です。普通のDFAの遷移処理に加え解析を高速化するための処理も入っているのでそれを理解して読まないと何が書いてあるかわかりません。こういう場合はそもそも何の解析をしているのかを考え、実装の詳細はあまり気にしないほうがいいです。とりあえず、

-非終端記号の場合はpush
-終端記号に来たらshift
-受理状態のままの場合はpopしてスタックが空になったらOK

という構文解析の基本が行われていると思っておけばよいです。

pushでは現在のスタックトップのノードに子ノードを追加して追加した子ノードを次にノードが追加される場合の親ノードとしています。(書いてるとややこしい)

#code(C){{
static int
push(stack *s, int type, dfa *d, int newstate, int lineno, int col_offset)
{
    int err;
    node *n;
    n = s->s_top->s_parent;
    assert(!s_empty(s));
    err = PyNode_AddChild(n, type, (char *)NULL, lineno, col_offset);
    if (err)
        return err;
    s->s_top->s_state = newstate;
    return s_push(s, d, CHILD(n, NCH(n)-1));
}
}}

shiftではpushは行われないので現在のスタックトップにノードが追加されていくことになります。

#code(C){{
static int
shift(stack *s, int type, char *str, int newstate, int lineno, int col_offset)
{
    int err;
    assert(!s_empty(s));
    err = PyNode_AddChild(s->s_top->s_parent, type, str, lineno, col_offset);
    if (err)
        return err;
    s->s_top->s_state = newstate;
    return 0;
}
}}

*構文解析してみる [#i3c050f6]

静的にコードを眺めるだけだとよくわからないので例によってノードを作ってみましょう。解析対象となるコードは以下。

#code(Python){{
[n for n in range(10) if n * n > 10]
}}


次のような感じになるはずです。

  single_input
    simple_stmt
      small_stmt
        expr_stmt
          testlist_star_expr
            test
              or_test
                and_test
                  not_test
                    comparison
                      expr
                        xor_expr
                          and_expr
                            shift_expr
                              arith_expr
                                term
                                  factor
                                    power
                                      atom_expr
                                        atom
                                          testlist_comp
                                            test
                                              or_test
                                                and_test
                                                  not_test
                                                    comparison
                                                      expr
                                                        xor_expr
                                                          and_expr
                                                            shift_expr
                                                              arith_expr
                                                                term
                                                                  factor
                                                                    power
                                                                      atom_expr
                                                                        atom
                                                                          NAME(n)
                                            comp_for
                                              expr_list
                                                expr
                                                  xor_expr
                                                    and_expr
                                                      shift_expr
                                                        arith_expr
                                                          term
                                                            factor
                                                              power
                                                                atom_expr
                                                                  atom
                                                                    NAME(n)
                                              or_test
                                                and_test
                                                  not_test
                                                    comparison
                                                      expr
                                                        xor_expr
                                                          and_expr
                                                            shift_expr
                                                              arith_expr
                                                                term
                                                                  factor
                                                                    power
                                                                      atom_expr
                                                                        atom
                                                                          NAME(range)
                                                                        trailer
                                                                          arglist
                                                                            argument
                                                                              test
                                                                                or_test
                                                                                  and_test
                                                                                    not_test
                                                                                      comparison
                                                                                        expr
                                                                                          xor_expr
                                                                                            and_expr
                                                                                              shift_expr
                                                                                                arith_expr
                                                                                                  term
                                                                                                    factor
                                                                                                      power
                                                                                                        atom_expr
                                                                                                          atom
                                                                                                            NUMBER(10)
                                              comp_iter
                                                test_nocond
                                                  or_test
                                                    and_test
                                                      not_test
                                                        comparison
                                                          expr
                                                            xor_expr
                                                              and_expr
                                                                shift_expr
                                                                  arith_expr
                                                                    term
                                                                      factor
                                                                        power
                                                                          atom_expr
                                                                            atom
                                                                              NAME(n)
                                                                      *
                                                                      factor
                                                                        power
                                                                          atom_expr
                                                                            atom
                                                                              NAME(n)
                                                          comp_op
                                                            >
                                                          expr
                                                            xor_expr
                                                              and_expr
                                                                shift_expr
                                                                  arith_expr
                                                                    term
                                                                      factor
                                                                        power
                                                                          atom_expr
                                                                            atom
                                                                              NUMBER(10)
      NEWLINE

長いわ!演算子の優先順位の都合でかなり深いノードになっています。

*おわりに [#j1b37ae4]

というわけでスクリプト解析まで来ました。シンプルなスクリプトなのにかなり深いノードになっています。Rubyの場合こんなに深くならなかったけど、yaccが演算子優先順位を処理してくれるからか。その代わり、Rubyではかっこ省略とかができる関係でparse.yの文法記述がノード作成処理込みで4000行以上になっていますね(2.3.0の場合)。慣れてるからRubyの方がよくない?と感じてますがたぶん構文解析処理的にはPythonの方がシンプルですね。ノードが深い問題は[[この後>Python/AST作成を読む]]を読んでいけば解決するのでしょう。


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS