B演習(言語処理系演習)第8回 12/5 評価器 田浦 gdbのインストール $ su (パスワード入力) # apt-get install gdb すでにそろっている材料 すでにそろっている材料 • 構文木(実行すべきプログラムの文面を表した データ構造) • Python値の表現や構築方法 • mk_py_int(5), mk_py_float(3.3), … 最終ステージ : 評価器 • プログラムを実行 言われたPython値を作ったり, 表示したり,etc. 概要 環境の概念 式を評価する • 演算子,組み込み関数の実装法 文を評価する 実行時エラーの表示 最終課題について いずれも規定課題の正しい実行+性能測定 選択肢 • ○ mini-Python • △ sub-Python = mini-Python – 辞書,リスト,タプル,文字列 • ◎ mini-Python + α • αの例 • 性能向上 • オリジナルなmini-Pythonプログラム(大きめ) • GC sub-Python サポートするデータ型が • 整数,浮動小数点数,None, 関数(Python, native) だけ • おまけとして for 文もなくなる(文字列,タプル,リ ストのどれかがないと実行できないため) • これでも評価器の大部分を作ることにはなるが, 組み込み関数・演算子の数が激減し,各々の演 算子の動作も単純になる 期待する進め方 × まずはとにかくレジュメを熟読してから… × まずは公開ソースを熟読してから… ○ • レジュメをまずざっと読む • 要するに完成すべきもの(要するにPythonプログラムが実 行できれば良い)が何かを理解する • 細かいことはさておき,動く「原理」を理解する.原理を理 解したらそれをもとにとにかく自分で作り始めてみる • 分からなくなったとき,正しい道を進んでいるか不安に なったときにレジュメを見る 式を評価する py_val_t eval_expr(expr_t e, …) 評価値のデータ表現 構文木 構文木評価値のデータ表現 全体像 py_val_t eval_expr(expr_t e, …) { switch (e->kind) { case expr_kind_var: … case expr_kind_literal_int: … case expr_kind_literal_float: … … } } 最も簡単な場合の例(intリテラル) case expr_kind_literal_int: return mk_py_int(e->u.lit_i); 構文木中にある整数 mini-Pythonのデータ表現への変換(pyvalues.c) 簡単にいかない例: 変数 case expr_kind_var: … e->u.var … ?? 構文木を見ただけではその変数の値は分か らない 「変数名 値に関する情報」が必要 環境 環境 変数名とその値の対応を保持しているもの ある式の評価値はそれだけでは定まらず,環境が あってはじめて値が決まる • 式を「環境の下」で評価する • 同じ変数名でも関数が違えば異なる値を保持している • それらは「環境が違う」 • eval_exprは「環境」を引数として受け取る 変数のスコープ規則(局所変数,大域変数)などを正 確に述べる上でも役に立つ概念 Pythonの変数,スコープ規則 局所変数と大域変数 z = 10 def f(): y = 20 def g(): z = 30 def h(): global z z = 40 大域変数zへの代入 局所変数yへの代入 局所変数zへの代入 大域変数zへの代入 変数参照 まず局所変数を,なければ大域変数を参照 する z = 10 def f(): z = 5 return z 局所変数zを参照 def g(): return z 大域変数zを参照 環境による説明 プログラム全体で唯一,大域環境が作られる 毎関数呼び出し時に,その呼び出し用の「局 所環境」が作られる あらゆる式・文は,局所環境,大域環境の下 で評価される(便宜上,トップレベルは局所環 境=大域環境と考える) eval_exprのインタフェース 局所環境 大域環境 py_val_t eval_expr(expr_t e, env_t lenv, env_t genv, stack_trace_t bt) スタックトレース(関数呼び出し履歴) エラーメッセージの表示用(後述) 変数への代入, global global x • 局所環境で「xは大域変数である」とマーク x=v • 局所環境のxをvにセット • ただし,xが大域変数であるとマークされていれば 大域環境のxをvにセット 変数の参照 局所環境を探索 見つからなければ,またはその変数が大域 変数であるとマークされていれば大域環境を 探索 見つかればそれが評価値 見つからなければ実行時エラー 環境のインタフェース env_t mk_env() env_set(env_t env, char* key, py_val_t val) py_val_t env_lookup(env_t env, char * key) void env_set_global(env_t env, char * key) int env_is_global(env_t env, char * key) 変数参照の評価(まとめ) py_val_t env_lookup_var( env_t lenv, env_t genv, char * key, …) { py_val_t v = env_lookup(lenv, key); if (v != py_val_not_found && v != py_val_global) return v; v = env_lookup(genv, key); if (v != py_val_not_found) return v; else エラー; } その他のケース expr_kind_display_tuple: /* [ a, b, c,...] */ 要素部を評価してデータを作る関数を呼ぶ (mk_py_tuple, /* etc.) expr_kind_display_list: [ a, b, c,...] */ sub-Pythonでは不要 expr_kind_display_dict: /* { a : x, b : y } */ expr_kind_paren: /* ( e ) */ カッコ内の式を評価する expr_kind_operator: /* e + e, etc. */ ほとんどの場合関数呼び出しの一種とみなせる(後述) expr_kind_subscript: /* e[e] */ expr_kind_attref: /* e.f */ 単独で現れたらエラー(後述) case expr_kind_call: /* e(e:e,..) */ 関数呼び出し式の評価 E0 (E1, …, En)の評価 (後でひとつだけ例外説 明) • E0を評価 py_val_t : f • E1, …, En を評価 py_val_vec_t : A • 場合1: f が native関数の場合対応するC関数 を呼び出す • 場合2: f が Python関数の場合後述 • 場合3: どちらでもない場合エラー Python関数の呼び出し 新しい環境を作る(e’ = mk_env()) その環境に「パラメータ名 : 渡された引数」を 登録する(env_set(e’, …)) その新しい局所環境の下で関数の本体(文の 列)を評価する(eval_stmt_vec(…, e’, …) E0がattref式の場合の例外 例: L.append(x) mini-Python固有の約束: • L.append(x) = append(L, x) • L.appendは構文としては,expr_kind_attrefという 種類の式として構文解析されるが,関数呼び出し の関数の位置以外に現れることを許さない • 関数呼び出しを評価する際にこの場合を特別扱 いする(レジュメ4.6節) 演算子,添え字式,などを関数呼び 出しとみなす 例: E0 + E1 • • • • 実際評価の方法は似ている E0 を評価 v0 E1 を評価 v1 v0 + v1を計算する(それほど簡単ではない) そこでこれを add(E0 , E1)という関数呼び出し だとみなして評価する addはどこかに(native関数,Python関数とし て)定義する 文の評価 py_val_t eval_stmt(stmt_t s, env_t lenv, env_t genv, stack_trace_t bt) 返り値の意味 • • • • py_val_continue continueで実行が終了した py_val_break breakで実行が終了した Pythonデータの表現 returnで実行が終了した py_val_next それ以外で(普通に)実行が終了し た 文の種類 stmt_kind_expression stmt_kind_assignment stmt_kind_pass stmt_kind_return stmt_kind_break: stmt_kind_continue: stmt_kind_del: stmt_kind_print: stmt_kind_global: stmt_kind_if: stmt_kind_while: stmt_kind_for: stmt_kind_fundef: 自明な3つ pass, break, continue • py_val_next, py_val_break, py_val_continueを返 すだけ 次に簡単な2つ expression • 式をeval_exprを用いて評価する • py_val_nextを返す return E • Eをeval_exprを用いて評価する • それを返す 「関数呼び出しとみなせる」文たち print E del E0[E1] E0[E1] = E 環境を書き換える文(1) global x • 局所環境でxが大域変数であるとマーク (env_set_global) x=E • Eを評価 v • 局所環境でxをvにセット • ただし,局所環境でxが大域変数であるとマークさ れていれば(env_is_global)大域環境でxをvにセッ ト 環境を書き換える文(2) def f(x, y, …): S Python関数(mk_py_ifun)を作って,環境に登 録 • 変数fへの代入と同じ効果 制御構造 if, while, for エラーメッセージの表示 エラー発生ソース位置 簡単なエラーメッセージ スタックトレース スタックトレースのインタフェース stack_trace_t mk_stack_trace() void stack_trace_push(stack_trace_t bt, char* name, src_pos_t call_site) • btに,「nameとい関数がソース位置call_siteで呼ばれた」と記録(push) void stack_trace_pop(stack_trace_t bt, char* name, src_pos_t call_site) • btから頂上のエントリをひとつ除去(pop)する.それは,「nameとい関数がソー ス位置call_siteで呼ばれた」というエントリでなくてはならない void print_stack_trace(stack_trace_t bt) まとめ:作る部品の全体像 各種演 算子, 組み込 み関数 環境(env_t) native関数群 eval_expr 関数呼び出し スタックトレース 実行時エラー表示 式文 eval_stmt Pythonで書かれた組み込 み関数,演算子,添え字 式,del, printに対応した関 数群 del, print, E[E] = E まとめ:概念として重要だったもの 環境 • これがないと変数を含む式・文は評価できない Python関数呼び出しの評価方法 • 新しい局所環境が作られる • 引数のあたいが局所環境に登録されて渡される 様々な種類の式・文が「関数呼び出しの一種」とみ なせる(要領よく実装) • 演算子,添え字,添え字に代入,del, print 文を評価した結果の返り値 • py_val_continue, break, next
© Copyright 2024 ExpyDoc