B演習(言語処理系演習)第一回

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)