B演習(言語処理系演習)第2回 田浦
今日の予定 言語処理系とは 大きなプログラムの書き方に関する一般論 今週の課題 部品化・モジュール化 部品の開発 分割コンパイル Makeによる自動化 Subversionによるソースの共有
言語処理系とは
言語処理系 プログラムP def fib(n): if n < 2: return 1 else: return … プログラムPの 動作・副作用 文字列
構文木 構文解析器 “fib” n 2 … def if < プログラムP def fib(n): if n < 2: return 1 else: return … 評価器 文字列 プログラムPの 動作・副作用
プログラムを書くときの考え方 「たくさんのことを一度にやらない」 言語処理系での例: 全体を,概念的に独立性の高い部品に分解する 部品単独の動作,部品間の相互作用を少なく,明確に 言語処理系での例: 構文解析器: 文字列を読み込んで構文木を作る 評価器: 構文木を実行 構文木の受け渡しが両者の唯一の相互作用 大きくなっても壊れない,維持・修正をしやすいプログラムを作る際の鉄則
def fib ( n ): if n < 2: return 1 else: return … 字句列(token stream) プログラムP def fib(n): if n < 2: return 1 else: return … 字句解析器 文字列 構文解析器 構文木 “fib” n 2 … def if < 評価器 プログラムPの 動作・副作用
おのおのの役割 字句解析器(tokenizer): 構文解析器(parser): 評価器(evaluator): 「文字列」から字句(単語)を認識.字句列を生成 正しくない字句(例: 1a)が現れたらエラー 構文解析器(parser): 字句列を構文木に変換 構文的に正しくない字句の列が現れたらエラー(parse error) 評価器(evaluator): 構文木をプログラムとして実行
構文木の実際 構文的に正しい任意のプログラムを表現できるデータ構造 本質的でない文字列としての違いを吸収する E.g., print 1 と print 1 さまざまな式,文に対して対応するtypedef (C++ class)を行って表現する
来週以降 字句解析器 構文解析器(+構文木の定義) 評価器 について順に説明していく
開発環境の選択肢 Unix的環境: Emacs + gcc (+ make, gdb, …) Microsoft的環境 折衷 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 生成したいファイルとその生成方法(コマンド)をMakefileに記述 生成したいファイルを生成するのに必要最小限のコマンドを自動的に実行 最小限のMakefile lc : lc.o cs.o gcc -o lc lc.o cs.o lc gcc -o lc lc.o cs.o lc.o cs.o gcc –c lc.c gcc –c cs.c lc.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 M-x shell M-x gdb C-o でバッファ間を移動 C-x ` (エラーの場所へ飛ぶ) M-x shell M-x gdb C-o でバッファ間を移動 (define-key global-map "\C-o" 'other-window)