B演習(言語処理系演習)第4回 田浦.

Slides:



Advertisements
Similar presentations
オブジェクト指向言語・ オブジェクト指向言語演習 中間試験回答例. Jan. 12, 2005 情報処理技術基礎演習 II 2 オブジェクト指向言語 中間試験解説 1  (1) 円柱の体積(円柱の体積 = 底面の円の面積 x 高さ) を求めるプログラムを作成しなさい。ただし、出力結果は、入 力した底面の円の半径.
Advertisements

アルゴリズムとデータ構造 第2回 線形リスト(復習).
15.1 文字列処理の基本 15.2 文字列処理用ライブラリ関数
東京工科大学 コンピュータサイエンス学部 亀田弘之
コンパイラ 2011年10月17日
数理情報工学演習第一C プログラミング演習 (第3回 ) 2014/04/21
プログラミング入門2 第10回 構造体 情報工学科 篠埜 功.
プログラミング入門2 第10回 構造体 情報工学科 篠埜 功.
画像ファイル(ppm)の読み書き 画像データ用のメモリ確保・解放
システムプログラミング 第5回 情報工学科 篠埜 功 ヒアドキュメント レポート課題 main関数の引数 usageメッセージ
基礎プログラミングおよび演習 第9回
最適化ソルバーのための Python言語入門
C言語 配列 2016年 吉田研究室.
プログラミング言語論 第6回 型 情報工学科 篠埜 功.
第2回:Javaの変数と型の宣言 プログラミングII 2007年10月2日.
理由:文字数より要素数の多い配列を用いた時に,文字列の最後を示すため
アルゴリズムとデータ構造 2011年6月13日
理由:文字数より要素数の多い配列を用いた時に,文字列の最後を示すため
第6章 2重ループ&配列 2重ループと配列をやります.
構造体.
プログラミング演習II 2004年12月 21日(第8回) 理学部数学科・木村巌.
アルゴリズムとデータ構造 第2回 線形リスト(復習その2).
プログラミング演習Ⅰ 課題2 10進数と2進数 2回目.
コンパイラ 2012年10月15日
コンパイラ 2012年10月22日
関数 関数とスタック.
画像ファイル(ppm)の読み書き 画像データ用のメモリ確保・解放
コンパイラ 2011年10月24日
ちょっとした練習問題① 配列iroを['R', 'W', 'R', 'R', 'W' , 'W' , 'W']を宣言して、「W」のときの配列の番号をprintfで表示するようなプログラムを記述しなさい。
B演習(言語処理系演習)第8回 評価器 田浦.
B演習(言語処理系演習)第3回 字句解析 田浦.
Cプログラミング演習 第7回 メモリ内でのデータの配置.
プログラミング入門 電卓を作ろう・パートIV!!.
プログラミング 4 記憶の割り付け.
2005年度 データ構造とアルゴリズム 第3回 「C言語の復習:再帰的データ構造」
プログラミング演習I 2003年5月7日(第4回) 木村巌.
第10章 これはかなり大変な事項!! ~ポインタ~
プログラミング入門2 第11回 情報工学科 篠埜 功.
プログラミング入門2 第11回 情報工学科 篠埜 功.
第7回 プログラミングⅡ 第7回
プログラミング基礎B 文字列の扱い.
プログラミング 4 木構造とヒープ.
コンパイラ 2011年10月20日
プログラミング言語論 第六回 理工学部 情報システム工学科 新田直也.
B演習(言語処理系演習)第2回 田浦.
C言語 はじめに 2016年 吉田研究室.
15.1 文字列処理の基本 15.2 文字列処理用ライブラリ関数
JavaScriptを含んだHTML文書に対する データフロー解析を用いた構文検証手法の提案
C++ 構文解析 構文解析器の状態保存と復元
プログラミング入門2 第6回 関数 情報工学科 篠埜 功.
情報基礎演習B 後半第2回 担当 岩村 TA 谷本君.
アルゴリズムとデータ構造 2012年6月11日
情報処理Ⅱ 第2回 2005年10月14日(金).
情報処理Ⅱ 第2回 2006年10月13日(金).
アルゴリズムとデータ構造1 2009年6月15日
ネットワーク・プログラミング Cプログラミングの基礎.
情報処理Ⅱ 第7回 2004年11月16日(火).
コンパイラ 2012年10月11日
プログラミング 4 文字列.
アルゴリズムとデータ構造 2010年6月17日
プログラミング入門2 第6回 関数 情報工学科 篠埜 功.
第7章 そろそろ int 以外も使ってみよう! ~データ型 double , bool~
第3回簡単なデータの入出力.
情報処理Ⅱ 2005年11月25日(金).
15.1 文字列処理の基本 15.2 文字列処理用ライブラリ関数
プログラミング入門2 第5回 配列 変数宣言、初期化について
プログラミング入門2 第3回 条件分岐(2) 繰り返し文 篠埜 功.
C言語講座第5回 2017 構造体.
岩村雅一 知能情報工学演習I 第7回(後半第1回) 岩村雅一
オブジェクト指向言語論 第六回 知能情報学部 新田直也.
Presentation transcript:

B演習(言語処理系演習)第4回 田浦

構文解析器 def fib ( n ): if n < 2: return 1 else: return … 字句列(token stream) 構文解析器 構文木 “fib” n 2 … def if <

全体像 データ型定義 コンストラクタ 構文解析器本体 各構文の種類(変数, 演算式, def, for, etc.)ごとに, その種類の構文木の定義 (typedef struct …) 式すべて, 文すべてを表す構文木の定義 コンストラクタ 各種類の構文木を作る関数 構文解析器本体 各構文の種類ごとに, 字句解析器から字句の列を受け取り, その構文木を作る関数

構文木の定義(文の例) typedef struct stmt_while { /* while文 */ … } stmt_while, * stmt_while_t; typedef struct stmt_for { /* for文 */ … } stmt_for, * stmt_for_t; typedef struct stmt_fundef { /* def文 */ … } stmt_fundef, * stmt_fundef_t; … typedef struct stmt { /* 文全体 */ stmt_kind_t k; /* 種類 */ union { … } } stmt, * stmt_t;

while文の例 typedef struct stmt_while { /* while文 */ expr_t e; /* 条件部分 */ stmt_vec_t body; /* 繰り返し本体 */ } stmt_while, * stmt_while_t このように, 個々の構文の種類に対し, その構成要素(while文であれば条件式と本体)をフィールドに持つような構造体を作る 文法定義を眺めれば, 個々の構造体がどのようなフィールドを持たねばならないかがわかる

コンストラクタ 個々の構文の種類ごとに, その種類の構文木を作るような関数を作る stmt_t mk_stmt_while(expr_t e, stmt_t s, …) { … } stmt_t mk_stmt_for(char * x, expr_t e, stmt_t s, …) { … } 注: …のところには, ソースコード上の位置情報など, 必要な情報を適宜追加することだろう

構文解析器の作成 以上の「準備」ができていると仮定して, 実際の構文解析(字句の列から構文木を作る)処理について考える

文法定義の読み方(1) 単純な連結 例 (while文): while_stmt ::= "while" expression ":" suite 読み方: while_stmt (とみなされる字句の列)とは, “while” (というただひとつの字句からなる列) expression (とみなされる字句の列), “:” (というただひとつの字句からなる列) suite (とみなされる字句の列) をこの順に並べたものである

文法定義の読み方(2) 複数選択肢 例 (文全体) statement ::= expression_stmt NEWLINE | assignment_stmt NEWLINE | … | while_stmt | for_stmt | funcdef statement (とみなされる字句の列)とは, assignment_stmt とみなされる字句の列とNEWLINEというただひとつの字句からなる列をこの順で連結したもの, または, …, または funcdefとみなされる字句の列, のいずれかである

構文解析器の仕組み概要(1) LL(1)文法用再帰下向き構文解析法 文法記号Aに対し,関数 parse_A(tokenizer_t t, …) を定義 渡されたtから生成されるいくつかの字句列がA(とみなされる字句列)であれば,そこまで字句列を読み込んで,Aを表す構造体を作って返す さもなければエラー(行番号など)を表示して終了(exit)

… … 構文解析器の仕組み概要(2) parse_A(t, …)開始時 t A? parse_A(t, …)終了時 t tの現在の字句(tok_cur_token_XXX(t)) parse_A(t, …)終了時 … t tの現在の字句(tok_cur_token_XXX(t))

各記号ごとの構文解析器の導出 文法規則から(機械的に)導く 基本アイデア A -> B C D に対し, parse_A(tokenizer_t t) { parse_B(t); parse_C(t); parse_D(t); } 注: 構文木を組み立てる部分は省略

複数の選択肢がある場合 例 : A -> B C D | E F G 現在の字句によってどちらをとるべきかを選択する parse_A(tokenizer_t t) { if (現在のtokenがB C Dの先頭になりうる) { parse_B(t); parse_C(t); parse_D(t); … } else if (E F Gの先頭になりうる) { parse_E(t); parse_F(t); parse_G(t); … } else parse_err(t); }

構文解析器の課題 プログラムを読み込み,構文木を作って,またそれをprintする main() { tree = parse_program(…); print_program(tree, …); } (空白やコメント以外は)まったく同じプログラムが出力されるはず 出力された文字列をもう一度同じプログラムに入れてみる.今度はまったく同一の文字列が出てくるはず

./parser a.py > b.py ./parser b.py > c.py diff b.py c.py 注: a.py と b.py はコメントや空白を除き同一 b.py と c.py は同一の文字列

自然なソースコードの構成 syntree.h : 構文木のtypedefおよびコンストラクタの宣言 syntree.c : 多数のコンストラクタ(mk_A)の定義 parser.h : トップレベルの構文解析器(parse_file_input)の宣言 parser.c : 多数の構文解析器(parse_A)の定義(syntree.hを#include)

いくつかのヘルプ 原理がわかっていても実際にたくさんの構造体定義や関数を書くのは大変なので少しテンプレートを提供する syntree.h almost_empty_parser.c parser.c において, 少数の解析木だけを説明用に残し, あとはほとんど空にしたもの 使うと作業の道筋がわかりやすくなるとは思うが, これの「解読」に明けくれないように注意(基本アイデアは説明したとおり)

プリント修正(HP上のPDFは修正済み) 資料(3) 字句解析器 p20. 4.4節 最後の行 cs_cur_lineを実現するために…  cs_cur_line_stringを実現するために… p21. 4.5節 tokenizer の定義内 char_buf_t str_buf; /* 現在の行の先頭の空白の数 */  char_buf_t str_buf; /* 現在の行(先頭から現在の文字まで)を保持 */ int_vec_t indents;  int_stack_t indents;

ここまでを前半の課題とする 締め切り … 詳しくはHPにアナウンスする(まだしてない) 「できました」報告は別途メール subversionに”parser”という名前のモジュールを登録 ソース&Makefile checkoutして “make” で“parser”という名前のプログラムができるようにしておく ./parser プログラムファイル名 でプログラムの構文解析を行い,その構文木をまたプログラムに戻して表示 「できました」報告は別途メール 班番号,checkoutすべきモジュール名を報告

C言語雑学 汎用コンテナ コンテナ(入れ物) 頻出(多くのプログラミング言語で,ライブラリとして提供,または組み込み) リスト,ハッシュ表,探索木,etc. 頻出(多くのプログラミング言語で,ライブラリとして提供,または組み込み) C++ STL Javaクラスライブラリ Python 辞書,リスト,タプル

コンテナの何が問題か コンテナ実装のロジックは,入れるものが何であってもほとんど同じ にもかかわらず,intの入れ物と,char *の入れ物を別々に定義しなくてはならない 例 : リスト of T (Tを入れるリスト) typedef struct T_list { … } * T_list_t; T_list_t mk_T_list(); void list_append(T_list_t l, T x); T list_get(T_list_t l, int i)

例 : リスト 整数のリスト char * のリスト int_list_t mk_int_list(); void int_list_append(int_list_t l, int x); int int_list_get(int_list_t l, int i); char * のリスト string_list_t mk_string_list(); void string_list_append(string_list_t l, char * x); char * string_list_get(string_list_t l, int i);

選択肢? [その1]: あきらめて似たコードを何度も書く [その2]: int_listだけを定義する.そして思い切って(笑), int_listにchar *を入れてみる int_list_t l = mk_int_list(); int_list_append(l, “hello”); char * x = int_list_get(l, 0); 実は,ある場合には[その2]が機能する

そもそも[その2]が(一般には)まずい根本的なの原因は,int型の領域(変数, 配列/構造体の要素)にchar *の値を代入するところにある 例: int_list_append(l, “hello”); しかし実際には,char *もintも32 bit整数であるので,実はこの代入によって情報が失われているわけではない char * p = “hello”; int x = p; char * y = x; /* y == p */

C言語における危険だが合法な代入 C言語ではこのような,「機械語上では可能」な代入を(自己責任で)合法としている コンパイラは警告を出すがエラーとはしない char * x = 1000; /* ポインタ := 整数 */ int a[10]; char * p = a; /* char* := int* */ f(int x) { … } f(“hello”); void* はgenericポインタとよばれ,「不明な型」へのポインタを保持するのに慣習的に用いられる

つまり,以下は合法 int_list_t l = mk_int_list(); int_list_append(l, “hello”); char * x = int_list_get(l, 0);

キャストを用いて黙らせることも可能 int_list_t l = mk_int_list(); int_list_append(l, (int)“hello”); char* x =(char*)int_list_get(l, 0); しかし, 無様 間違いを犯す元(一般に,リストに何(int? char*?)が入っているのかを区別する手段がない

ベターな慣習 “汎用版”を一度だけ定義 それらを用いて個々の型に対するコンテナを定義 中身はvoid*としておくのが良い generic_list_t mk_generic_list(); generic_list_append(generic_list_t l, void * x); generic_list_get(generic_list_t l, int i); それらを用いて個々の型に対するコンテナを定義

例 int_list の定義 typedef struct int_list * int_list_t; int_list_t mk_int_list() { return (int_list_t)mk_generic_list(); } int int_list_append(int_list_t l, int x) { generic_list_append((generic_list_t)l, (void *)x); } int int_list_get(int_list_t l, int i) { return (int)generic_list_get((generic_list_t)l, i); }

注: 要するにどんな代入も合法か? もちろん否 ポインタ := 浮動小数点数 ポインタ := 構造体 整数 := 構造体 注: 「構造体へのポインタ」ではなく 構造体A := 構造体B