# だいがくいも 進捗報告 ## 第2回(10/7) - チームを結成した。 ## 第3回(10/11) - チーム名を決めた。 - とりあえずpythonをいじってみることになった。 - make時に以下のエラーが出たがそのまま進めた。(大矢) ```bash /home/denjo/daigakuimo/python/Python-3.10.0/Modules/_ctypes/_ctypes.c:107:10: fatal error: ffi.h: そのようなファイルやディレクトリはありません #include <ffi.h> ``` - ビルドは一応できた。(ちゃんと.gitignoreをつくる!) - 動作確認として、Hello Worldをprintしてみる。(井浦) ```bash print("Hello World") ``` と書いたtest.pyをつくり ```bash $ ./python3 test.py ``` を実行。きちんとHello Worldが出力された。 - emacsでデバッガを用いて上記のHello Worldを追いかけてみる。(井浦) /home/denjo/Python_install/binをカレントディレクトリとしてemacsを立ち上げ、M-x gud-gdbを実行すると、 ``` Run gud-gdb (like this): gdb --fullname python3-config ``` となるが、-configの部分は不要なので消す。その後、 ``` (gdb) b main (gdb) r test.py ``` とすると、テキスト通りに動く。 ## 第4回(10/12) - pythonの逆アセンブラする方法を見つけた。  (参考:https://docs.python.org/3/library/dis.html) - [CPythonの構文解析入門](https://blog.xoxzo.com/ja/2021/06/10/reading-cpython-source-code-02/#fn:1) を参考に, 文法を規定する python.gram を変更する練習をしてみる。しかし, 以下のようなエラーが出てきた。 ```bash ~/python/Python-3.10.0$ make regen-pegen PYTHONPATH=./Tools/peg_generator python3 -m pegen -q c \ ./Grammar/python.gram \ ./Grammar/Tokens \ -o ./Parser/parser.new.c File "./Grammar/python.gram", line 648 a=sum 'akira' b=term { _PyAST_BinOp(a, Add, b, EXTRA) } ^ SyntaxError: pegen parse failure For full traceback, use -v make: *** [Makefile:864: regen-pegen] エラー 1 ``` 追記: 最初の ' | ' を入れていなかった... ``` | a=sum 'akira' b=term { _PyAST_BinOp(a, Add, b, EXTRA) } ``` ## 第5回(10/14) - [checklist](https://devguide.python.org/grammar/)にそってとりあえずやってみる。(大矢) -> `$ make regen-pegen`でエラーが出た。 -> pythonのバージョンが古かった(泣) -> ubuntu20.04にしてpython3.8にバージョンアップしたら解決 - 手動で変更するファイル一覧(まだあるかも?) - Grammer/python.gram - Parser/Python.asdl - Python/symtable.c - Python/ast.c - Lib/ast.py (変えなくてもいいかも) - Python/ast_opt.c - Python/compile.c - `$ make regen-all` - Grammer / python.gram: 78行 ``` | 'breakall' { _PyAST_Breakall(EXTRA) } ``` `$ make regen-pegen`で Parser を自動生成してもらう - Parser / Python.asdl: 51行 ``` | Pass | Break | Breakall | Continue ``` `$ make regen-ast`で AST を自動生成してもらう - Python / compile.c: break 文を追跡すると, compiler_break という関数を発見。ここで, ジャンプしていると考えられる。とりあえず, 同じように compiler_breakall という関数を追加する。 ```c static int compiler_breakall(struct compiler *c) { struct fblockinfo *loop = NULL; /* Emit instruction with line number */ ADDOP(c, NOP); if (!compiler_unwind_fblock_stack_zero(c, 0, &loop)) { return 0; } if (loop == NULL) { return compiler_error(c, "'breakall' outside loop"); } if (!compiler_unwind_fblock(c, loop, 0)) { return 0; } ADDOP_JUMP(c, JUMP_ABSOLUTE, loop->fb_exit); NEXT_BLOCK(c); return 1; } ``` 実際に目的地のアドレスを渡しているのは compiler_unwind_fblock_stack 関数なので, ここも変える必要がある。ループを抜けた先のアドレスをスタックで積んでいき, 内側から順に渡しているようである。breakall は全ループを抜けたいので, 問答無用で一番外側のループに対応するアドレスを返すようにすれば良い。つまり, u_fblock[c->u->u_nfblocks-1] を u_fblock[0] に書き変える。 (取り出されなかったデータがどうなるのかはわからない...) ```c static int compiler_unwind_fblock_stack_zero(struct compiler *c, int preserve_tos, struct fblockinfo **loop) { if (c->u->u_nfblocks == 0) { return 1; } struct fblockinfo *top = &c->u->u_fblock[0]; if (loop != NULL && (top->fb_type == WHILE_LOOP || top->fb_type == FOR_LOOP)) { *loop = top; return 1; } struct fblockinfo copy = *top; c->u->u_nfblocks--; if (!compiler_unwind_fblock(c, &copy, preserve_tos)) { return 0; } if (!compiler_unwind_fblock_stack(c, preserve_tos, loop)) { return 0; } c->u->u_fblock[c->u->u_nfblocks] = copy; c->u->u_nfblocks++; return 1; } ``` さらに, 3615行に以下を追加する。 ```c case Breakall_kind: return compiler_breakall(c); ``` - Python / ast.c: 852行 - Python / ast_opt.c: 764行 - Python / symtable.c: 1428行 ```c case Breakall_kind: ``` make して, 二重ループを用いた以下の python プログラムを実行してみる。 ```python= # loop.py for i in range(3): for j in range(3): print(i,j) if i == 1 and j == 1: breakall print("fin1") print("fin2") ``` ```bash ~/python_install/bin$ ./python3 loop.py 0 0 0 1 0 2 fin1 1 0 1 1 fin2 ``` 正しく動作している!!(と思われる) ## 第6回(10/18) - 現状の実装では、forループとwhileループ以外のframe block(try, exceptなど)に対応していないので、その修正が必要。 - 次の目標:breakで抜けるループの数を指定出来るようにする!! (例:`break(3)`でループを3つ分抜ける) -> そのためには、引数をわたすのがcompile.cの中のどこで行われているのかを調べなきゃいけない。 - スタックには while, for 文のデータだけでなく, try-except, with 文などのデータも含まれる。これらには break は使えないので, 単純に一番外側のデータを渡すだけではエラーが生じる。例えば, 以下のような python プログラムの場合, SyntaxError となる。 ```python= # try.py i = 0 try: while 1: if i == 1: breakall i += 1 except: print("except") ``` ```bash ~/python_install/bin$ ./python3 try.py File "/home/denjo/python_install/bin/try.py", line 6 breakall ^^^^^^^^ SyntaxError: 'breakall' outside loop ``` よって, 一番外側のデータの fb_type が 'WHILE_LOOP' または 'FOR_LOOP' であることを確認しないといけない。compiler_unwind_fblock_stack 関数でも if 文の中で次のように fb_type を確認していた。 ```c if (loop != NULL && (top->fb_type == WHILE_LOOP || top->fb_type == FOR_LOOP)) { *loop = top; return 1; } ``` これを参考に, compiler_unwind_fblock_stack_zero 関数を以下のように書き換えて, try.pyを再実行してみる。しかし, やはり SyntaxError は解消されなかった。 ```c static int compiler_unwind_fblock_stack_zero(struct compiler *c, int preserve_tos, struct fblockinfo **loop) { if (c->u->u_nfblocks == 0) { return 1; } struct fblockinfo *top; for (int i = 0; i < c->u->u_nfblocks; ++i) { if ((c->u->u_fblock[i].fb_type == WHILE_LOOP) || (c->u->u_fblock[i].fb_type == FOR_LOOP)) { top = &c->u->u_fblock[i]; break; } } if (loop != NULL) { *loop = top; return 1; } return 0; } ``` ## 第7回(10/19) - ↑の問題について、調べるとfor文の中に入れてない模様、なぜだ、、、 ->解決したっぽい(変更を更新できてなかった?) ## 第8回(10/21) - break(n)の実装について、compile.c内でnにアクセスできれば、breakallの実装を参考にbreak(n)も実装できそうだが、引数へのアクセスの仕方がわからない。そもそもcompile.cの中からはアクセスできないのかも? ⇒できました(笑) - breaknewを追加しました。変更点は以下です。 - Grammer/python.gram - L79 ``` | &'breaknew' breaknew_stmt ``` - L396 ``` breaknew_stmt[stmt_ty]: | 'breaknew' a=atom { _PyAST_Breaknew(a, EXTRA) } ``` - Parser/Python.asdl - L52 ``` | Breaknew(expr value) ``` - Python/symtable.c - L1429 ```c case Breaknew_kind: ``` - Python/ast.c - L853 ```c case Breaknew_kind: ret = validate_expr(state, stmt->v.Breaknew.value, Load); break; ``` - Python/ast_opt.c - L757 ```c case Breaknew_kind: CALL(astfold_expr, expr_ty, node_->v.Breaknew.value); break; ``` - Python/compile.c - L1914 ```c static int compiler_unwind_fblock_stack_new(struct compiler *c, int count, struct fblockinfo **loop) { if (c->u->u_nfblocks == 0) { return 1; } struct fblockinfo *top; for (int i = (c->u->u_nfblocks) - 1 ; i >= 0 ; --i){ if (c->u->u_fblock[i].fb_type == WHILE_LOOP || c->u->u_fblock[i].fb_type == FOR_LOOP){ count--; if (count <= 0){ top = &c->u->u_fblock[i]; break; } } } if (loop != NULL) { *loop = top; return 1; } return 1; } ``` - L3110 ```c static int compiler_breaknew(struct compiler *c, stmt_ty s){ struct fblockinfo *loop = NULL; ADDOP(c, NOP); if (!compiler_unwind_fblock_stack_new(c, PyLong_AS_LONG(s->v.Breaknew.value->v.Constant.value), &loop)) { return 0; } if (loop == NULL) { return compiler_error(c, "'breaknew' outside loop"); } if (!compiler_unwind_fblock(c, loop, 0)) { return 0; } ADDOP_JUMP(c, JUMP_ABSOLUTE, loop->fb_exit); NEXT_BLOCK(c); return 1; } ``` - L3657 ```c case Breaknew_kind: return compiler_breaknew(c, s); ``` - breaknewの現状や問題点は以下 - Python.gramのatomのとこが、もっと適した型があるかも? - symtableで変更が最小限なのは意図的なものです。はじめはまわりに合わせて変更したけど、makeのときにエラーが出るので最小限にしました。 - 求)正式なコマンド名 - breaknewは暫定です。 - `break 3`みたいに回数指定されたときだけbreaknewの動作をするみたいな実装もあり。ただし、その場合はbreakallもbreakにうまく統合したほうが実装はきれい。 - 試したテストファイルは以下 ```Python= # test21.py for i in range(10): while(1): while(1): breaknew 2 print(i) print("fin") ``` ![](https://i.imgur.com/e1aun4b.png) - エラー出力について、上記のtest21.pyでbreaknewに与える数字を以下に変更してみた - `breaknew n` のように変数を与えたとき ⇒ `TypeError: 'str' object cannot be interpreted as an integer` - 負の数を与えたとき ``` breaknew -1 ^ SyntaxError: invalid syntax ``` - 0~3を与えたとき ⇒ 正常に実行される。もちろん0, 1のときはwhileループを抜けられずにプログラムは終了しなくなる。 - 4以上を与えたとき ⇒ `Segmentation fault (コアダンプ)`   なぜ、、、? - 小数を与えたとき ⇒ `TypeError: 'float' object cannot be interpreted as an integer` - ↑上のエラー出力をもうちょっと整備したいよね、特に4以上のところ - さらなるバグを発見。以下の python プログラムの場合, 無限ループになり実行が終わらない。一番内側の k ループが while ならきちんと終了する。 - 追記(大矢) 一番内側とbreaknewの先がともにforだったときにバグるみたいです。調べてみると、breaknewして1行目に行ったときに、1行目ではなく3行目のrange(3)を参照してしまっているっぽい。 - さらに追記(大矢) バグの修正完了。現在27時をまわっているので詳しいことは実験の時間に話そうと思います。簡単にいうと、breaknewですっとばした部分の後処理をするために再帰的に実装し直しました。多分breakallの方も再帰実装にし直さないとバグると思います。(試してないけど例えばfinallyあたりとか) - またさらに追記(大矢) ローカルでbreakallも再帰実装し直しました。一部関数名も変更しています(unwind系、可読性のため)。また、上記の「指定する数がループ数を超えたときにセグフォする」という問題点も解決されていました。breaknewのほうと合わせてgitにpushするのはまだしていません(念のため)。 ```Python= for i in range(3): for j in range(3): for k in range(3): print(i,j,k) breaknew 2 print("fin") ``` ```bash 0 0 0 1 0 0 1 0 0 ... 1 0 0 free(): corrupted unsorted chunks 中止 (コアダンプ) ``` ## 第9回(10/25) - break, breakall, breaknew を統合して break で実行できるように変更した。 - break ⇒ 普通のbreak(ループを1個抜ける) - break 3 ⇒ ループを3個抜ける - break 0 ⇒ ループを全部抜ける - ループ数以上の数字が与えられた時のエラーメッセージの変更。 - 残りはスライド作成時間。 ## 第10回(10/26) - 発表の日。がんばろう!