ファイルを指定したときに呼び出されるPyParser_ParseFileObject からスクリプト解析を見ていきます。(ファイルを指定しないでpythonコマンドを起動したときも呼ばれる)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| - | | | | | - | | ! | | | | | ! | |
parsetok関数は長いです。エラー処理をばさっと削ると以下のような感じ。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| - | | | | - | | | | | | | | | | | | | | | | | | | | | | - - | | ! | ! ! | - | | ! | | | | | | | ! | |
パーサを作り、トーカナイザからトークンを取得して追加、終わりまで来たらループ終了、(エラーがなかったら)ノードができているのでそれを返すということをしています。
1
2
3
4
5
6
7
8
9
10
11
12
13
| - | | | | | | | | | | ! | |
DFAとあります。つまり、スクリプトからノードに変換するにはオートマトンを使っているようです。yaccを使っていないのはインデントが意味を持っているから?
grammer、その実体は_PyParser_GrammarはPython/graminit.cに定義されています。もっともDFAを表現したデータが格納されているだけなのでこれだけ見てもよくわかりません。graminit.c(とInclude/graminit.h)はParserにあるpgenを用いて生成されているようです。また、pgen自体の入力ファイルはGrammar/Grammarです。これを見るとPythonの文法が書かれています。
PyParser_AddTokenではトークンと現在の状態(どの文法要素を解析中か)からノードを構築しています。書いてある処理ははっきり言って難解です。普通のDFAの遷移処理に加え解析を高速化するための処理も入っているのでそれを理解して読まないと何が書いてあるかわかりません。こういう場合はそもそも何の解析をしているのかを考え、実装の詳細はあまり気にしないほうがいいです。とりあえず、
という構文解析の基本が行われていると思っておけばよいです。
pushでは現在のスタックトップのノードに子ノードを追加して追加した子ノードを次にノードが追加される場合の親ノードとしています。(書いてるとややこしい)
1
2
3
4
5
6
7
8
9
10
11
12
13
| - | | | | | | | | | ! | |
shiftではpushは行われないので現在のスタックトップにノードが追加されていくことになります。
1
2
3
4
5
6
7
8
9
10
11
| - | | | | | | | ! | |
静的にコードを眺めるだけだとよくわからないので例によってノードを作ってみましょう。解析対象となるコードは以下。
1 | |
次のような感じになるはずです。
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)
長いわ!演算子の優先順位の都合でかなり深いノードになっています。
というわけでスクリプト解析まで来ました。シンプルなスクリプトなのにかなり深いノードになっています。Rubyの場合こんなに深くならなかったけど、yaccが演算子優先順位を処理してくれるからか。その代わり、Rubyではかっこ省略とかができる関係でparse.yの文法記述がノード作成処理込みで4000行以上になっていますね(2.3.0の場合)。慣れてるからRubyの方がよくない?と感じてますがたぶん構文解析処理的にはPythonの方がシンプルですね。ノードが深い問題はこの後を読んでいけば解決するのでしょう。