Pythonにunless文を追加する
※このブログはeeicの実験「大規模ソフトウェアを手探る」のレポートとして書かれたものです。
リンク
- 概要(Pythonを改造して機能を追加した話)
- PythonにC言語っぽい文法を追加する
・論理演算子として&&や||や!を追加する
・論理値のTrue,Falseをtrue,falseにも対応させる
・elifだけでなくelse ifにも対応させる - Pythonにオートインデント機能をつける
- (おまけ)Pythonにunless文を追加する ←イマここ
導入
実験をするにあたって、まずは先輩のunless文を追加してみたのレポートを参考にすることにしました。しかしPythonのバージョンの違いによりうまく行きませんでした。
実験終了後、途中まで手探ったunless
文を今なら実装できるのではと思いいじってみたらうまくいったのでおまけとして記録しておきたいと思います。
流れ
Pythonのコンパイルの流れは上記の公式ドキュメントに記されています。簡単に書くと以下の通りです。
ソースコード
↓ Parser/tokenizer.c
トークン列
↓ Parser/parser.c
AST(抽象構文木)
↓ Python/compile.c
CFG(制御フローグラフ)
↓ Python/compile.c
バイトコード(Pythonの仮想マシンが読み取るアセンブリ言語のようなもの)
ここで、ソースコードからASTへの変換をいじるには、Grammar/python.gram
やGrammar/Tokens
に変更を加え、make regen-token
やmake regen-pegen
コマンドで関連ファイルを書き換えます(PythonにC言語っぽい文法を追加するを参照)。また、ASTからバイトコードへの変換はPython/compile.c
に変更を加えればよさそうです。
パーサの変更
PythonにC言語っぽい文法を追加するでも書いたとおり、Grammar/python.gram
を変更してunless
を解析できるようにします。ここではif
の定義を参考にしたいので、ファイル内検索でif
文に関連した部分を探しました。
Grammar/python.gram
compound_stmt[stmt_ty]: | &('def' | '@' | ASYNC) function_def | &'if' if_stmt | &'unless' unless_stmt | &('class' | '@') class_def | &('with' | ASYNC) with_stmt | &('for' | ASYNC) for_stmt | &'try' try_stmt | &'while' while_stmt (省略) if_stmt[stmt_ty]: | 'if' a=named_expression ':' b=block c=elif_stmt { _Py_If(a, b, CHECK((asdl_stmt_seq*)_PyPegen_singleton_seq(p, c)), EXTRA) } | 'if' a=named_expression ':' b=block c=[else_block] { _Py_If(a, b, c, EXTRA) } elif_stmt[stmt_ty]: | 'elif' a=named_expression ':' b=block c=elif_stmt { _Py_If(a, b, CHECK(_PyPegen_singleton_seq(p, c)), EXTRA) } | 'elif' a=named_expression ':' b=block c=[else_block] { _Py_If(a, b, c, EXTRA) } | 'else''if' a=named_expression ':' b=block c=elif_stmt { _Py_If(a, b, CHECK(_PyPegen_singleton_seq(p, c)), EXTRA) } | 'else''if' a=named_expression ':' b=block c=[else_block] { _Py_If(a, b, c, EXTRA) } else_block[asdl_stmt_seq*]: 'else' ':' b=block { b } unless_stmt[stmt_ty]: | 'unless' a=named_expression ':' b=block c=elun_stmt { _Py_Unless(a, b, CHECK((asdl_stmt_seq*)_PyPegen_singleton_seq(p, c)), EXTRA) } | 'unless' a=named_expression ':' b=block c=[else2_block] { _Py_Unless(a, b, c, EXTRA) } elun_stmt[stmt_ty]: | 'elun' a=named_expression ':' b=block c=elun_stmt { _Py_Unless(a, b, CHECK(_PyPegen_singleton_seq(p, c)), EXTRA) } | 'elun' a=named_expression ':' b=block c=[else2_block] { _Py_Unless(a, b, c, EXTRA) } | 'else''unless' a=named_expression ':' b=block c=elun_stmt { _Py_Unless(a, b, CHECK(_PyPegen_singleton_seq(p, c)), EXTRA) } | 'else''unless' a=named_expression ':' b=block c=[else2_block] { _Py_Unless(a, b, c, EXTRA) } else2_block[asdl_stmt_seq*]: 'else' ':' b=block { b }
変更をしてmake regen-pegen
とmake
をすると、以下のようなエラーが出ました。
Parser/parser.c:4012:20: error: implicit declaration of function ‘_Py_Unless’; did you mean ‘_Py_alias’? [-Werror=implicit-function-declaration] _res = _Py_Unless ( a , b , CHECK ( ( asdl_stmt_seq * ) _PyPegen_singleton_seq ( p , c ) ) , EXTRA ); ^~~~~~~~~~ _Py_alias
implicit declaration of function ‘_Py_Unless’
、つまり_Py_Unlessが宣言されていないようです。_Py_Ifがどこかに宣言されていないかと思い、grep _Py_If -r .
で検索をかけてみると、Include/Python-ast.h
にありました。ここで再び公式ドキュメントを参照すると、Parser/Python.asdl
を変更してmake regen-ast
を実行することでInclude/Python-ast.h
などの関連ファイルが書き換えられるようです。以下のように変更してみます。
Parser/Python.asdl
| If(expr test, stmt* body, stmt* orelse) | Unless(expr test, stmt* body, stmt* orelse)
make regen-ast
とmake
をするとビルドに成功しました!
コンパイラの変更
ここまででソースコードをASTに変換することはできました。しかし意味を定義していないので、unless
文のプログラムを実行しても何も表示されません。次はPython/compile.c
を変更します。
ファイルを見てみると、compiler_if関数でif文が定義されているようです。compiler_ifで検索すると2ヶ所見つかるので、それを参考にcompiler_unlessを追加します。コメントを読んで、constant
の真偽値は逆になるようにしました。
Python/compile.c
static int compiler_unless(struct compiler *c, stmt_ty s) { basicblock *end, *next; int constant; assert(s->kind == Unless_kind); end = compiler_new_block(c); if (end == NULL) return 0; constant = expr_constant(s->v.Unless.test); /* constant = 1: "unless 1", "unless 2", ... * constant = 0: "unless 0" * constant = -1: rest */ if (constant == 1) { //真偽値を反転 BEGIN_DO_NOT_EMIT_BYTECODE VISIT_SEQ(c, stmt, s->v.Unless.body); END_DO_NOT_EMIT_BYTECODE if (s->v.Unless.orelse) { VISIT_SEQ(c, stmt, s->v.Unless.orelse); } } else if (constant == 0) { VISIT_SEQ(c, stmt, s->v.Unless.body); if (s->v.Unless.orelse) { BEGIN_DO_NOT_EMIT_BYTECODE VISIT_SEQ(c, stmt, s->v.Unless.orelse); END_DO_NOT_EMIT_BYTECODE } } else { if (asdl_seq_LEN(s->v.Unless.orelse)) { next = compiler_new_block(c); if (next == NULL) return 0; } else { next = end; } if (!compiler_jump_if(c, s->v.Unless.test, next, 0)) { return 0; } VISIT_SEQ(c, stmt, s->v.Unless.body); if (asdl_seq_LEN(s->v.Unless.orelse)) { ADDOP_JUMP(c, JUMP_FORWARD, end); compiler_use_next_block(c, next); VISIT_SEQ(c, stmt, s->v.Unless.orelse); } } compiler_use_next_block(c, end); return 1; } (省略) case If_kind: return compiler_if(c, s); case Unless_kind: return compiler_unless(c, s);
make
してpython3を実行してみると、unless
はif文と同じ挙動をしました。もう一度compiler_unless関数内をよく見ると、compiler_jump_if関数というものがあり、4つ目の引数がcond
となっていることがわかります。これは条件だとにらんで0を1に変えてみると…
Python/compile.c
static int compiler_unless(struct compiler *c, stmt_ty s) { basicblock *end, *next; int constant; assert(s->kind == Unless_kind); end = compiler_new_block(c); if (end == NULL) return 0; constant = expr_constant(s->v.Unless.test); /* constant = 1: "unless 1", "unless 2", ... * constant = 0: "unless 0" * constant = -1: rest */ if (constant == 1) { //真偽値を反転 BEGIN_DO_NOT_EMIT_BYTECODE VISIT_SEQ(c, stmt, s->v.Unless.body); END_DO_NOT_EMIT_BYTECODE if (s->v.Unless.orelse) { VISIT_SEQ(c, stmt, s->v.Unless.orelse); } } else if (constant == 0) { VISIT_SEQ(c, stmt, s->v.Unless.body); if (s->v.Unless.orelse) { BEGIN_DO_NOT_EMIT_BYTECODE VISIT_SEQ(c, stmt, s->v.Unless.orelse); END_DO_NOT_EMIT_BYTECODE } } else { if (asdl_seq_LEN(s->v.Unless.orelse)) { next = compiler_new_block(c); if (next == NULL) return 0; } else { next = end; } if (!compiler_jump_if(c, s->v.Unless.test, next, 1)) { //4つ目の引数condを0から1にする return 0; } VISIT_SEQ(c, stmt, s->v.Unless.body); if (asdl_seq_LEN(s->v.Unless.orelse)) { ADDOP_JUMP(c, JUMP_FORWARD, end); compiler_use![](https://i.imgur.com/0ZPZAAT.png) _next_block(c, next); VISIT_SEQ(c, stmt, s->v.Unless.orelse); } } compiler_use_next_block(c, end); return 1; } (省略) case If_kind: return compiler_if(c, s); case Unless_kind: return compiler_unless(c, s);
できました!