pythonを改変してdowhile文を使えるようにしてみた
はじめに
この記事は, 東京大学工学部電子情報工学科・電気電子工学科(通称EEIC)の3年後期実験の1つである「大規模ソフトウェアを手探る」のレポートとして書かれたものです.
「大規模ソフトウェアを手探る」という実験では, 2, 3人のチームを組み, 大規模なオープンソースソフトウェア(OSS)を1つ以上選んで, 機能を拡張したり, バグを修正するという実験です. 私達の班「cuatro」では, pythonを改変するOSSとして選定し, 実際に機能拡張や改変を行ってみました.
概要
私達の班「cuatro」では, 以下の3点の改変を行いました.
- 全角スペースを含んだときに出るエラーメッセージを改変する.
- do-while文を実装する.
- classのメソッドの引数の省略を試みる.
本記事では2つ目の「do-while文を実装する」というものの説明です.
なお, 1つ目と3つ目については, 班のメンバーが以下の記事を書いています.興味のある方は合わせてご覧ください.
1つ目「エラーメッセージの変更」
https://massiilo.hatenablog.com/entry/2021/11/07/064957
3つ目「クラスにおいて、 selfを省略可能にする」
https://rachmanix.hatenablog.com/entry/2021/11/05/183056
なお, 改変したコードについては, 以下の本実験のgithubリポジトリにあります.
https://doss-gitlab.eidos.ic.i.u-tokyo.ac.jp/cuatro/python
本記事の「do-while文を実装する」については, ディレクトリhkatori/cpython
にあります.
環境
- OS : Ubuntu 18.04 LTS
インストール
cpythonを以下のgithubリポジトリよりcloneしました. ターミナルからgitコマンドを用いて以下のようにします.
$ git clone https://github.com/python/cpython.git
すると, カレントディレクトリに名称が衝突しなければ, cpython
というディレクトリができるはずです.
以下は, 使用したgithubリポジトリのリンクです.
https://github.com/python/cpython
do-whileとは
通常のwhile文では条件判定を最初に行いますが, do-while文では, 条件判定を処理が終わった後の最後に行います. ですので, 以下のpythonコードではwhile文では, 一度も処理がなされないのに対し, do-while文では, 一回だけ処理がなされます.
a = 5 while(a > 5): print(a) #処理は一度も実行されない dowhile(a > 5): print(a) #処理は一度だけ実行される
このようにdo-while文では, 少なくとも一回は処理を行うことが特徴としてあげられ, 例えば, パスワードの入力を求めるシステム(入力してもらって一致判定を行い, 必要であれば, 一致しない場合はもう一度入力をユーザに求める)の処理は正しくdo-while文そのものです.
なお, do-whileはwhile文を用いて以下のように書けます. また, pythonにはdo-while文は実装されていません. c言語やc++にはあります.
a = 5 while True: print(a) if (a > 5): break
cpythonとは
c言語で記述されているpythonのリファレンス実装のことです. 作者によって作られたバージョンで, 通常のpythonはこれを指すことが多いです.
手探る
ビルド
まず, cloneしたcpythonが正しく動作するか確認します.
改変するpythonが入っているディレクトリcpython
に遷移します.
$ cd cpython
コンパイルに関するいろいろな設定や, 環境の情報の入手, インストール場所の決定を行うのがconfigureです. なお, オプションとして, -O0
, -g
を指定していますが, デバッガで追跡するためにつけると便利なもので, 前者は最適化レベルを最低に落とし, 後者は実行可能ファイルにデバッグシンボル(コンパイル後の命令列とソースコード上の位置の対応関係)をつけるオプションです. またprefix
オプションでインストール先のフォルダを指定しています.
$ CFLAGS="-O0 -g" ./configure --prefix=/home/denjo/cpython_install
次にコンパイルとインストールを行います.
$ make $ make install
これでビルドは完了です. 実行して正しく動作することが確認できました.
大まかな流れ
Python Developer's Guideという公式のドキュメントや過去にpythonを手探った先輩たちのブログを参考に, どのようなことが必要で, どのような処理をしているのかを把握しました. ざっくりとまとめると以下のような感じになります.
Python Developer's Guide
ここで, 上の図のディレクトリ名/ファイル名は改変の必要な可能性のあるファイルです. 今回の改変では上の図の青色から黄色の部分までが変更対象で, バイトーコードから機械語(CPUが理解できる言語)までの変更は不要です.
CFG(Control Flow Graph) : プログラムを実行したときに, 通る可能性のある経路をグラフ化したもの.
改変
Grammar/Python.gram
このファイルは簡単に言うとpythonの文法を定義しているものです.
今回は新しくdo-while文を追加したいので, do-whileの定義を追加することになります. しかし, do-whileは処理手順が違うことがwhileとの違いなので, より具体的には, whileでは条件判定を最初に行いますが, do-whileでは条件判定を最後に行います. ですので, 定義自体はwhileの真似をすれば大丈夫で, Python.gram
ではwhileを真似してdo-whileのバージョンを作れば良いだけです.
grep
コマンドでwhile関連の定義がなされている箇所をPython.gram
から探します.
ここでは, ファイル中にwhile_stmt
という文字列が入っている箇所を検索します. オプションとしては以下を指定しています.
-r
: ディレクトリ以下を探索します(今回の場合はカレントディレクトリがGrammar
なので,Grammar
以下のファイルを探索します).-n
: 行数を表示します.-I
: バイナリファイルをマッチするデータを含まないかのように処理します.
$ grep -r -n -I "while_stmt" python.gram:135: | &'while' while_stmt python.gram:364:while_stmt[stmt_ty]: python.gram:365: | invalid_while_stmt python.gram:1229:invalid_while_stmt:
どうやら追加すべき箇所は135行目付近, 364行目付近, 1229行目付近の3箇所のようです. それぞれの箇所を見て, 空気を読んでdo-whileを追加していきます.
- 135行目付近.
Before
compound_stmt[stmt_ty]: | &('def' | '@' | ASYNC) function_def | &'if' if_stmt | &('class' | '@') class_def | &('with' | ASYNC) with_stmt | &('for' | ASYNC) for_stmt | &'try' try_stmt | &'while' while_stmt | match_stmt
After
compound_stmt[stmt_ty]: | &('def' | '@' | ASYNC) function_def | &'if' if_stmt | &('class' | '@') class_def | &('with' | ASYNC) with_stmt | &('for' | ASYNC) for_stmt | &'try' try_stmt | &'while' while_stmt | &'dowhile' dowhile_stmt | match_stmt
- 364行目付近
Before
# While statement # --------------- while_stmt[stmt_ty]: | invalid_while_stmt | 'while' a=named_expression ':' b=block c=[else_block] { _PyAST_While(a, b, c, EXTRA) } # For statement # -------------
After
# While statement # --------------- while_stmt[stmt_ty]: | invalid_while_stmt | 'while' a=named_expression ':' b=block c=[else_block] { _PyAST_While(a, b, c, EXTRA) } # DoWhile statement #------------------ dowhile_stmt[stmt_ty]: | invalid_dowhile_stmt | 'dowhile' a=named_expression ':' b=block c=[else_block] { _PyAST_DoWhile(a, b, c, EXTRA) } # For statement # -------------
- 1229行目付近
Before
invalid_while_stmt: | 'while' named_expression NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") } | a='while' named_expression ':' NEWLINE !INDENT { RAISE_INDENTATION_ERROR("expected an indented block after 'while' statement on line %d", a->lineno) } invalid_for_stmt:
After
invalid_while_stmt: | 'while' named_expression NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") } | a='while' named_expression ':' NEWLINE !INDENT { RAISE_INDENTATION_ERROR("expected an indented block after 'while' statement on line %d", a->lineno) } invalid_dowhile_stmt: | 'dowhile' named_expression NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") } | a='dowhile' named_expression ':' NEWLINE !INDENT { RAISE_INDENTATION_ERROR("expected and indented block after 'dowhile' statement on line %d", a->lineno) } invalid_for_stmt:
さらに, Python Developer's GuideによるとこのGrammar/Python.gram
と言うファイルを改変したら以下のコマンドを実行してねとの記述があったのでその通りにします. 公式リファレンスによると, どうやらこのコマンドを実行することでパーサ(parser.c
)を自動的に再生成しているようです.
$ make regen-pegen
しかし, 残念なことに下記のようなエラーを吐かれました.
PYTHONPATH=./Tools/peg_generator python3 -m pegen -q c \ ./Grammar/python.gram \ ./Grammar/Tokens \ -o ./Parser/parser.new.c Traceback (most recent call last): File "/usr/lib/python3.6/runpy.py", line 193, in _run_module_as_main "__main__", mod_spec) File "/usr/lib/python3.6/runpy.py", line 85, in _run_code exec(code, run_globals) File "/home/denjo/python/hkatori/cpython/Tools/peg_generator/pegen/__main__.py", line 15, in <module> from pegen.build import Grammar, Parser, ParserGenerator, Tokenizer File "/home/denjo/python/hkatori/cpython/Tools/peg_generator/pegen/build.py", line 9, in <module> from pegen.c_generator import CParserGenerator File "/home/denjo/python/hkatori/cpython/Tools/peg_generator/pegen/c_generator.py", line 3, in <module> from dataclasses import dataclass, field ModuleNotFoundError: No module named 'dataclasses' Makefile:966: recipe for target 'regen-pegen' failed make: *** [regen-pegen] Error 1
これについては, 過去にpythonを手探った先輩の中に同じ現象に遭遇した方がいたので, その人の対処法を参考にしました(いや, ありがたい). https://qiita.com/koooooo/items/b21d87ffe2b56d0c589b
どうやら, デフォルトで自分の環境にインストールされているpythonのバージョンが3.6.9
なのですが, それだとこのmake regen-pegen
コマンドは動かない部分があるので, 新しいバージョンのpythonで実行する必要があるようです.
ということで以下のサイトの手順に従い, 新しいバージョンのpythonをインストールしました.
https://www.python.jp/install/ubuntu/index.html
$ sudo apt install $ sudo apt install build-essential libbz2-dev libdb-dev \ libreadline-dev libffi-dev libgdbm-dev liblzma-dev \ libncursesw5-dev libsqlite3-dev libssl-dev \ zlib1g-dev uuid-dev tk-dev
より最新バージョンである3.10.0をダウンロードし、解答してディレクトリPython-3.10.0
ができるのでそこに入って
$ ./configure $ make $ make install
$ python3 -V Python 3.10.0
よりちゃんとインストールできていることが確認されました. そして, もう一回regen-pegenします.
$ make regen-pegen PYTHONPATH=./Tools/peg_generator python3 -m pegen -q c \ ./Grammar/python.gram \ ./Grammar/Tokens \ -o ./Parser/parser.new.c python3 ./Tools/scripts/update_file.py ./Parser/parser.c ./Parser/parser.new.c
エラーは出ませんでした. 上手くいったようです.
Grammar/Tokens
中身を見ると>=
や}
などといった演算子などのトークンを定義しているようなので, 新しい関数を追加することに対して, 新しいタイプのトークンは生成していないので, 変更の必要はないです. 一応regen-token
しておきます. やっても益はあっても害はないはず.
$ make regen-token # Regenerate Doc/library/token-list.inc from Grammar/Tokens # using Tools/scripts/generate_token.py python3 ./Tools/scripts/generate_token.py rst \ ./Grammar/Tokens \ ./Doc/library/token-list.inc # Regenerate Include/token.h from Grammar/Tokens # using Tools/scripts/generate_token.py python3 ./Tools/scripts/generate_token.py h \ ./Grammar/Tokens \ ./Include/token.h # Regenerate Parser/token.c from Grammar/Tokens # using Tools/scripts/generate_token.py python3 ./Tools/scripts/generate_token.py c \ ./Grammar/Tokens \ ./Parser/token.c # Regenerate Lib/token.py from Grammar/Tokens # using Tools/scripts/generate_token.py python3 ./Tools/scripts/generate_token.py py \ ./Grammar/Tokens \ ./Lib/token.py
Parser/Python.asdl
ASTを構成する過程のもので, ブロックの形での記述をしています. こちらもgrepで該当箇所を探して, whileの真似をしてdo-whileのバージョンを作るだけです.
$ grep -n While "Python.asdl" 34: | While(expr test, stmt* body, stmt* orelse)
34行目に追記します.
Before
| While(expr test, stmt* body, stmt* orelse) | If(expr test, stmt* body, stmt* orelse)
After
| While(expr test, stmt* body, stmt* orelse) | DoWhile(expr test, stmt* body, stmt* orelse) | If(expr test, stmt* body, stmt* orelse)
orelseはつけることにしました. orelseとは、while文の条件式が偽になったときにelse句の内容が実行されます. これは, 手探って初めて知った仕様でした. これも大規模なソフトウェアを手探ることで得られる副産物です.
以下はorelseの説明です. orelseはforやifでも定義されています. 確かにelseは少なくともifがないと呼ばれないので, ifでorelseが定義されているのも納得ですし, むしろelseはそれ単体としてではなく, ifやforなどと共に定義されて然るべきだと思いました.
while 条件: 実行1 else: 実行2
無論, なくても処理は書けますが, あった方が便利な例として, もしwhile文の処理にbreakを仕込んで、実際にbreakしたときにはelseが実行されないようなことが考えられます.
公式ドキュメント通りregen-ast
コマンドを実行します.
$ make regen-ast # Regenerate 3 files using using Parser/asdl_c.py: # - Include/internal/pycore_ast.h # - Include/internal/pycore_ast_state.h # - Python/Python-ast.c /bin/mkdir -p ./Include /bin/mkdir -p ./Python python3 ./Parser/asdl_c.py \ ./Parser/Python.asdl \ -H ./Include/internal/pycore_ast.h.new \ -I ./Include/internal/pycore_ast_state.h.new \ -C ./Python/Python-ast.c.new Python/Python-ast.c.new, Include/internal/pycore_ast.h.new, Include/internal/pycore_ast_state.h.new regenerated. python3 ./Tools/scripts/update_file.py ./Include/internal/pycore_ast.h ./Include/internal/pycore_ast.h.new python3 ./Tools/scripts/update_file.py ./Include/internal/pycore_ast_state.h ./Include/internal/pycore_ast_state.h.new python3 ./Tools/scripts/update_file.py ./Python/Python-ast.c ./Python/Python-ast.c.new
Python/ast.c
こちらもgrep
で該当箇所を探して変更します.
Before
case While_kind: ret = validate_expr(state, stmt->v.While.test, Load) && validate_body(state, stmt->v.While.body, "While") && validate_stmts(state, stmt->v.While.orelse); break; case If_kind:
After
case While_kind: ret = validate_expr(state, stmt->v.While.test, Load) && validate_body(state, stmt->v.While.body, "While") && validate_stmts(state, stmt->v.While.orelse); break; case DoWhile_kind: ret = validate_expr(state, stmt->v.DoWhile.test, Load) && validate_body(state, stmt->v.DoWhile.body, "DoWhile") && validate_stmts(state, stmt->v.DoWhile.orelse); break; case If_kind:
Parser/tokenizer.c
grep
してみたところ, 変更が必要な箇所はないようです.
Parser/parser.c
Grammar/Python.gram
でも述べた通り, regen-pegen
コマンドで自動再生成したので変更不要です.
Python/symtable.c
構文解析パートのファイルの変更は終わったので, この項からはAST->CFG->バイトコードにおけるファイルの変更です.
symtable.c
は, ASTからシンボルの表を作成するファイルのようです. grep
で変更箇所を特定します.
$ grep -n While_kind symtable.c 1310: case While_kind:
Before
case While_kind: VISIT(st, expr, s->v.While.test); VISIT_SEQ(st, stmt, s->v.While.body); if (s->v.While.orelse) VISIT_SEQ(st, stmt, s->v.While.orelse); break; case If_kind:
After
case While_kind: VISIT(st, expr, s->v.While.test); VISIT_SEQ(st, stmt, s->v.While.body); if (s->v.While.orelse) VISIT_SEQ(st, stmt, s->v.While.orelse); break; case DoWhile_kind: VISIT(st, expr, s->v.DoWhile.test); VISIT_SEQ(st, stmt, s->v.DoWhile.body); if (s->v.DoWhile.orelse) VISIT_SEQ(st, stmt, s->v.DoWhile.orelse); break; case If_kind:
Python/compile.c
今までは定義を新たに追加するようなことばかりしてきましたが, これが本丸です. このファイルでは, ファイル名が示唆する通り, ASTからバイトコードを生成するファイルです.
しかし, やることはまず, grep
でwhile関連が書かれているところを探してdo-whileのバージョンを追記するという今までのやり方は変わりません.
$ grep -n while compile.c 240:The u pointer points to the current compilation unit, while units 385: while (PyUnicode_READ_CHAR(privateobj, ipriv) == '_') 614: while (b != NULL) { 1424: while (_PySet_NextEntry(o, &pos, &item, &hash)) { 3025:compiler_while(struct compiler *c, stmt_ty s) 3402: while (1) { 3628: return compiler_while(c, s); 4704: The LC/SC version returns the populated container, while the GE version is 5858: while (pc->fail_pop_size < size) { 5888: while (--pc->fail_pop_size) { 6322: while (icontrol--) { 6355: while (rotations--) { 6693: while (sp != stack) { 6906: while (todo > todo_stack) { 7094: while (ldelta > 127) { 7100: while (ldelta < -127) { 7108: while (bdelta > 254) { 7296: } while (extended_arg_recompile); 7308: while (PyDict_Next(dict, &pos, &k, &v)) { 7327: while (PyDict_Next(dict, &pos, &k, &v)) { 7417: while (PyDict_Next(c->u->u_varnames, &pos, &k, &v)) { 7433: while (PyDict_Next(c->u->u_cellvars, &pos, &k, &v)) { 7447: while (PyDict_Next(c->u->u_freevars, &pos, &k, &v)) { 7625: while (PyDict_Next(c->u->u_cellvars, &pos, &varname, &cellindex)) { 8136: while (inst->i_target->b_iused == 0) { 8428: while (next && next->b_iused == 0) { 8477: while (bb->b_instr[i].i_target->b_iused == 0) { 8495: while (sp > stack) { 8524: while (next->b_iused == 0 && next->b_next) { 8536: while (target->b_iused == 0) { 8735: while (b->b_next && b->b_next->b_iused == 0) {
$ grep -n While compile.c 1758: case While_kind: 1759: res = find_ann(st->v.While.body) || 1760: find_ann(st->v.While.orelse); 3039: if (!compiler_jump_if(c, s->v.While.test, anchor, 0)) { 3044: VISIT_SEQ(c, stmt, s->v.While.body); 3046: if (!compiler_jump_if(c, s->v.While.test, body, 1)) { 3053: if (s->v.While.orelse) { 3054: VISIT_SEQ(c, stmt, s->v.While.orelse); 3627: case While_kind:
どうやら変更箇所は, 1758行目, 3025行目, 3627行目付近の3箇所のようです.
- 1758行目付近.
Before
case While_kind: res = find_ann(st->v.While.body) || find_ann(st->v.While.orelse); break; case If_kind:
After
case While_kind: res = find_ann(st->v.While.body) || find_ann(st->v.While.orelse); break; case DoWhile_kind: res = find_ann(st->v.DoWhile.body) || find_ann(st->v.DoWhile.orelse); break; case If_kind:
- 3628行目付近.
Before
case While_kind: return compiler_while(c, s); case If_kind:
After
case While_kind: return compiler_while(c, s); case DoWhile_kind: return compiler_dowhile(c, s); case If_kind:
- 3025行目付近.
compile.c
はWhile test : body:
のようにブロックごとに分割したものを訪ねる順序を記し, バイトコードに変更する役割を担っています. つまり, while文とdo-while文の違いは条件分岐を前置するか後置するかの違いだったので, 以下のように, jump_if
関数とVISIT_SEQ
関数の順序を入れ替えることで, body(処理)を先に, test(条件分岐)を後にできます. これによりdo-while文は実現できます.
Before
static int compiler_while(struct compiler *c, stmt_ty s) { basicblock *loop, *body, *end, *anchor = NULL; loop = compiler_new_block(c); body = compiler_new_block(c); anchor = compiler_new_block(c); end = compiler_new_block(c); if (loop == NULL || body == NULL || anchor == NULL || end == NULL) { return 0; } compiler_use_next_block(c, loop); if (!compiler_push_fblock(c, WHILE_LOOP, loop, end, NULL)) { return 0; } if (!compiler_jump_if(c, s->v.While.test, anchor, 0)) { return 0; } compiler_use_next_block(c, body); VISIT_SEQ(c, stmt, s->v.While.body); SET_LOC(c, s); if (!compiler_jump_if(c, s->v.While.test, body, 1)) { return 0; } compiler_pop_fblock(c, WHILE_LOOP, loop); compiler_use_next_block(c, anchor); if (s->v.While.orelse) { VISIT_SEQ(c, stmt, s->v.While.orelse); } compiler_use_next_block(c, end); return 1; } static int compiler_return(struct compiler *c, stmt_ty s) {
After
static int compiler_while(struct compiler *c, stmt_ty s) { basicblock *loop, *body, *end, *anchor = NULL; loop = compiler_new_block(c); body = compiler_new_block(c); anchor = compiler_new_block(c); end = compiler_new_block(c); if (loop == NULL || body == NULL || anchor == NULL || end == NULL) { return 0; } compiler_use_next_block(c, loop); if (!compiler_push_fblock(c, WHILE_LOOP, loop, end, NULL)) { return 0; } if (!compiler_jump_if(c, s->v.While.test, anchor, 0)) { return 0; } compiler_use_next_block(c, body); VISIT_SEQ(c, stmt, s->v.While.body); SET_LOC(c, s); if (!compiler_jump_if(c, s->v.While.test, body, 1)) { return 0; } compiler_pop_fblock(c, WHILE_LOOP, loop); compiler_use_next_block(c, anchor); if (s->v.While.orelse) { VISIT_SEQ(c, stmt, s->v.While.orelse); } compiler_use_next_block(c, end); return 1; } static int compiler_dowhile(struct compiler *c, stmt_ty s) { basicblock *loop, *body, *end, *anchor = NULL; loop = compiler_new_block(c); body = compiler_new_block(c); anchor = compiler_new_block(c); end = compiler_new_block(c); if (loop == NULL || body == NULL || anchor == NULL || end == NULL) { return 0; } compiler_use_next_block(c, loop); if (!compiler_push_fblock(c, WHILE_LOOP, loop, end, NULL)) { return 0; } #if 1 compiler_use_next_block(c, body); VISIT_SEQ(c, stmt, s->v.DoWhile.body); SET_LOC(c, s); if (!compiler_jump_if(c, s->v.DoWhile.test, anchor, 0)) { return 0; } if (!compiler_jump_if(c, s->v.DoWhile.test, body, 1)) { return 0; } #endif #if 0 if (!compiler_jump_if(c, s->v.DoWhile.test, anchor, 0)) { return 0; } compiler_use_next_block(c, body); VISIT_SEQ(c, stmt, s->v.DoWhile.body); SET_LOC(c, s); if (!compiler_jump_if(c, s->v.DoWhile.test, body, 1)) { return 0; } #endif compiler_pop_fblock(c, WHILE_LOOP, loop); compiler_use_next_block(c, anchor); if (s->v.DoWhile.orelse) { VISIT_SEQ(c, stmt, s->v.DoWhile.orelse); } compiler_use_next_block(c, end); return 1; } static int compiler_return(struct compiler *c, stmt_ty s) {
再ビルド
変更が終わったので, ビルドします.
$ CFLAGS=-O0 -g" ./configure --prefix=/home/denjo/python/hkatori/cpython/cpython_install1 $ make $ make install
エラー出力や警告などは出ず, ビルドすることができました.
結果
テストコード
以下のようなテストコードを作成しました.
#do-while文を実装する #whileとdo-whileの違いがわかるケース print("do-while loop") a1 = 5 dowhile (a1 > 5): print("a1 is {0}".format(a1)) #出力される print("while loop") a2 = 5 while (a2 > 5): print("a2 is {0}".format(a2)) #出力されない #do-whileがwhile文としての挙動も示すか確認するケース print("======================") b1 = 5 dowhile(b1 > 0): print("b1 is {0}".format(b1)) #5, 4, 3, 2, 1が出力 if (b1 < 3): print("{0} is smaller than 3".format(b1)) #2, 1が出力 b1 -= 1 else: print("while else is OK") #出力される
1つ目のループでは, do-whileは条件判定を最後に行うので, 5だけが出力されるはずです. 2つ目のループではwhile文なので, 条件判定は最初に行われるからwhile文内の処理は実行されません. 3つ目のループは繰り返し処理が実行されているかを確認する目的と, orelseが機能しているかを確認する目的があります.
実行
cpython/cpython_install1/bin
ディレクトリに遷移します. 先程のpythonのテストケースをtestcase.py
と名づけ, cpython/cpython_install1/bin
に配置します. 実行すると以下のようになり, 正しく動作していることがわかります.
$ ./python3 testcase.py do-while loop a1 is 5 while loop ====================== b1 is 5 b1 is 4 b1 is 3 b1 is 2 2 is smaller than 3 b1 is 1 1 is smaller than 3 while else is OK
なお, 以下でも動作します.
$ ./python3.11 testcase.py $ python3 testcase.py
参考にした資料
以下, 本実験において, 参考にした資料を列挙します.
本実験のHP
https://doss.eidos.ic.i.u-tokyo.ac.jp
python公式ドキュメント
pythonと格闘した先輩方の記事
どのように改変すれば良いか, 様々な指針を与えてもらいました. ありがとうございます.
以上, 参考にしたブログなどを記載しました. ここに掲載しなかった他の方のブログもありました.
感想
正直, 最初は膨大なファイルサイズのソフトウェアやC言語で書かれているにしろpythonの言語処理系を手探るということで自分にできるのか不安なところがありました.
デバッガでただひたすらブレークポイントを立てて, 関数の中に入って, 出力を確認するという作業を延々とこなしても問題解決の糸口が見られず焦りが生じました.
Python Developer's Guideや過去の先輩方のブログを読むことを通じてこのフォルダやファイルは全体から見てどのような処理をしているのか, 文法を変更するにはこのファイルを変更するのが良いなどの情報を得ることができ, 全体像を概ね把握することにより, 改変は進んでいきました.
このブログを読む限りでは, なんの苦労もなく, 目的のファイルに一目散に辿り着き, コードの中身も把握して必要な変更や追記をしているように見えるかもしれませんが, 実際はそうではなく公式ドキュメントと格闘しながら, 特に最後のcompile.c
ではcompiler_while
関数を真似たcompiler_dowhile
関数を弄ってはビルドして出力を確認するという行為を繰り返しています.
最後に, 本実験において, 田浦先生, TAの方々には詰まったりしたところで色々と教えていただきました. ありがとうございました.