B演習(言語処理系演習)第2回 田浦 今日の予定 言語処理系とは 大きなプログラムの書き方に関する一般論 • 部品化・モジュール化 今週の課題 • • • • 部品の開発 分割コンパイル Makeによる自動化 CVSによるソースの共有 言語処理系とは プログラムP def fib(n): if n < 2: return 1 else: return … 文字列 言語処理系 プログラムPの 動作・副作用 構文木 プログラムP def fib(n): if n < 2: return 1 else: return … def “fib” n if 構文解析器 < n 文字列 プログラムPの 動作・副作用 評価器 … 2 プログラムを書くときの考え方 「たくさんのことを一度にやらない」 • 全体を,概念的に独立性の高い部品に分解する • 部品単独の動作,部品間の相互作用を少なく,明確に 言語処理系での例: • 構文解析器: 文字列を読み込んで構文木を作る • 評価器: 構文木を実行 • 構文木の受け渡しが両者の唯一の相互作用 大きくなっても壊れない,維持・修正をしやすいプロ グラムを作る際の鉄則 プログラムP def fib(n): if n < 2: return 1 else: return … def fib ( n ): if n < 2: return 1 else: return … 字句解析器 字句列(token stream) 文字列 構文木 def “fib” n < n 評価器 構文解析器 if … 2 プログラムPの 動作・副作用 おのおのの役割 字句解析器(tokenizer): • 「文字列」から字句(単語)を認識.字句列を生成 • 正しくない字句(例: 1a)が現れたらエラー 構文解析器(parser): • 字句列を構文木に変換 • 構文的に正しくない字句の列が現れたらエラー (parse error) 評価器(evaluator): • 構文木をプログラムとして実行 構文木の実際 構文的に正しい任意のプログラムを表現でき るデータ構造 • 本質的でない文字列としての違いを吸収する • E.g., print 1 と print 1 さまざまな式,文に対して対応するtypedef (C++ class)を行って表現する 来週以降 • 字句解析器 • 構文解析器(+構文木の定義) • 評価器 について順に説明していく 開発環境の選択肢 Unix的環境: Emacs + gcc (+ make, gdb, …) • Linux, cygwin Microsoft的環境 • Visual studioの統合環境 折衷 • Visual studioのコマンドラインコンパイラ(cl) • エディタはEmacs ウォームアップ課題 開発環境を決めて使ってみる 部品「文字ストーリムライブラリ」を作る それを使って「ファイルの行数カウント」プログラムを 作る 分割コンパイル makeの利用 Emacsを統合環境として利用する 目標: 部品化の実例と,中サイズ以上のプログラム の開発サイクルに慣れる 部品: 文字ストリームライブラリ 機能: 指定されたファイル中の文字を順に読 んでいく.常に • 「最後に読まれた文字」 • 「その文字の現れた行番号,列番号」 • を管理している 文字を読み込みながら,時々「現在の行番 号・列番号」を知りたい インタフェース(外部仕様) typedef char_stream { … } char_stream, * char_stream_t; char_stream_t cs = mk_char_stream(char * filename); void char_stream_next_char(cs); int char_stream_cur_char(cs); int char_stream_cur_line(cs); int char_stream_cur_column(cs); lc.c (1) void line_count(char * filename) { /* char_streamを作る */ char_stream_t cs = mk_char_stream(filename); if (cs == 0) { fprintf(stderr, "could not open %s\n", filename); exit(1); } while (char_stream_cur_char(cs) != EOF) { /* EOFが出るまで読んでいく */ char_stream_next_char(cs); } /* char_streamの仕様により,EOFの行番号がファイルの行数 */ printf("%d lines\n", char_stream_cur_line(cs)); } lc.c (2) int main(int argc, char ** argv) { if (argc != 2) { fprintf(stderr, "usage: %s filename\n", argv[0]); exit(1); } line_count(argv[1]); return 0; } 分割コンパイル 単にファイルを分けるだけでは× • 部品(char_stream)のtypedef, 関数の宣言をヘッダ ファイル(cs.h)へ • 部品(char_stream)の関数の定義(本体)をcs.cへ • 利用者はcs.hをinclude コンパイルの実際 • gcc –c cs.c • gcc –c lc.c • gcc –o lc lc.o cs.o Make 分割コンパイルの簡略化・自動化 • 生成したいファイルとその生成方法(コマンド)を Makefileに記述 • 生成したいファイルを生成するのに必要最小限 のコマンドを自動的に実行 lc 最小限のMakefile • lc : lc.o cs.o gcc -o lc lc.o cs.o gcc -o lc lc.o cs.o lc.o gcc –c lc.c lc.c cs.o gcc –c cs.c cs.c もう少し模範的なMakefile CFLAGS = -Wall OBJS = lc.o cs.o lc : $(OBJS) gcc -o lc $(OBJS) clean : rm -f $(OBJS) Cコンパイラオプション ファイル追加に応じて追加 make cleanで.oファイルを削除 (最初からコンパイルしなおすとき) まめ知識: Emacsの統合環境化 M-x compile • C-x ` (エラーの場所へ飛ぶ) M-x shell M-x gdb C-o でバッファ間を移動 • (define-key global-map "\C-o" 'other-window)
© Copyright 2024 ExpyDoc