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